Core OAuth 2.0 client utilities for Tekcify Auth
npm install @tekcify/auth-core-clientCore OAuth 2.0 client utilities for Tekcify Auth. This package provides low-level OAuth helpers that can be used across different frameworks and environments (browser, Node.js, React, Vue, etc.).
``bash`
npm install @tekcify/auth-core-clientor
pnpm add @tekcify/auth-core-clientor
yarn add @tekcify/auth-core-client
This package provides the foundational OAuth 2.0 functionality for Tekcify Auth, including:
- PKCE (Proof Key for Code Exchange) generation for secure OAuth flows
- OAuth Client class for managing authentication flows
- Standalone functions for framework-agnostic usage
- Type definitions for TypeScript support
- Centralized auth server URL via AUTH_SERVER_URL constant
The package uses a centralized auth server URL that can be imported and configured:
`typescript
import { AUTH_SERVER_URL } from '@tekcify/auth-core-client';
console.log(AUTH_SERVER_URL);
`
The default auth server URL is http://localhost:7002. You can modify this constant if needed for your environment.
PKCE is required for secure OAuth flows, especially for public clients (SPAs, mobile apps).
`typescript
import { generateCodeVerifier, generateCodeChallenge } from '@tekcify/auth-core-client';
// Generate a code verifier (43-128 characters)
const codeVerifier = generateCodeVerifier();
// Generate the code challenge (SHA256 hash)
const codeChallenge = await generateCodeChallenge(codeVerifier, 'S256');
// Store the verifier securely (you'll need it later)
localStorage.setItem('code_verifier', codeVerifier);
`
`typescript
import { buildAuthorizeUrl } from '@tekcify/auth-core-client';
const authUrl = buildAuthorizeUrl({
clientId: 'your-client-id',
redirectUri: 'https://yourapp.com/callback',
scopes: ['read:profile', 'write:profile'],
state: crypto.randomUUID(), // CSRF protection
codeChallenge: codeChallenge,
codeChallengeMethod: 'S256',
});
// Redirect user to authUrl
window.location.href = authUrl;
`
After the user is redirected back with an authorization code:
`typescript
import { exchangeCode } from '@tekcify/auth-core-client';
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
if (state !== expectedState) {
throw new Error('Invalid state parameter');
}
const codeVerifier = localStorage.getItem('code_verifier');
const tokens = await exchangeCode({
code: code!,
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
redirectUri: 'https://yourapp.com/callback',
codeVerifier: codeVerifier!,
});
localStorage.setItem('access_token', tokens.accessToken);
localStorage.setItem('refresh_token', tokens.refreshToken);
`
Access tokens expire. Use the refresh token to get a new one:
`typescript
import { refreshAccessToken } from '@tekcify/auth-core-client';
const newTokens = await refreshAccessToken({
refreshToken: storedRefreshToken,
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
});
// Update stored tokens
localStorage.setItem('access_token', newTokens.accessToken);
localStorage.setItem('refresh_token', newTokens.refreshToken);
`
`typescript
import { getUserInfo } from '@tekcify/auth-core-client';
const userInfo = await getUserInfo(accessToken);
console.log(userInfo.email); // appdever01@gmail.com
console.log(userInfo.name); // John Doe
console.log(userInfo.email_verified); // true
`
For a more object-oriented approach, use the OAuthClient class:
`typescript
import { OAuthClient } from '@tekcify/auth-core-client';
const client = new OAuthClient({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
redirectUri: 'https://yourapp.com/callback',
scopes: ['read:profile', 'write:profile'],
});
// Build authorization URL
const authUrl = await client.buildAuthorizeUrl({
state: 'random-state',
codeChallenge: codeChallenge,
codeChallengeMethod: 'S256',
});
// Exchange code
const tokens = await client.exchangeCode(code, codeVerifier);
// Refresh token
const newTokens = await client.refreshAccessToken(refreshToken);
// Get user info
const userInfo = await client.getUserInfo(accessToken);
// Revoke token (logout)
await client.revokeToken(accessToken);
// Introspect token (check if valid)
const introspection = await client.introspectToken(accessToken);
if (introspection.active) {
console.log('Token is valid');
}
`
Here's a complete example of implementing the OAuth flow:
`typescript
import {
OAuthClient,
generateCodeVerifier,
generateCodeChallenge,
} from '@tekcify/auth-core-client';
// Initialize client
const client = new OAuthClient({
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.CLIENT_SECRET!,
redirectUri: process.env.REDIRECT_URI!,
scopes: ['read:profile'],
});
// Step 1: Start login flow
async function login() {
const verifier = generateCodeVerifier();
const challenge = await generateCodeChallenge(verifier, 'S256');
const state = crypto.randomUUID();
// Store verifier and state securely
sessionStorage.setItem('oauth_verifier', verifier);
sessionStorage.setItem('oauth_state', state);
// Build and redirect to authorization URL
const authUrl = await client.buildAuthorizeUrl({
state,
codeChallenge: challenge,
codeChallengeMethod: 'S256',
});
window.location.href = authUrl;
}
// Step 2: Handle callback
async function handleCallback() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
const storedState = sessionStorage.getItem('oauth_state');
const verifier = sessionStorage.getItem('oauth_verifier');
// Verify state
if (state !== storedState) {
throw new Error('Invalid state parameter');
}
if (!code || !verifier) {
throw new Error('Missing authorization code or verifier');
}
// Exchange code for tokens
const tokens = await client.exchangeCode(code, verifier);
// Store tokens securely
localStorage.setItem('access_token', tokens.accessToken);
localStorage.setItem('refresh_token', tokens.refreshToken);
localStorage.setItem('token_expires_at', String(Date.now() + tokens.expiresIn * 1000));
// Clean up
sessionStorage.removeItem('oauth_verifier');
sessionStorage.removeItem('oauth_state');
return tokens;
}
// Step 3: Make authenticated requests
async function fetchProtectedData() {
let accessToken = localStorage.getItem('access_token');
const expiresAt = parseInt(localStorage.getItem('token_expires_at') || '0');
// Refresh if expired
if (Date.now() >= expiresAt) {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) {
throw new Error('No refresh token available');
}
const newTokens = await client.refreshAccessToken(refreshToken);
accessToken = newTokens.accessToken;
localStorage.setItem('access_token', newTokens.accessToken);
localStorage.setItem('refresh_token', newTokens.refreshToken);
localStorage.setItem('token_expires_at', String(Date.now() + newTokens.expiresIn * 1000));
}
// Use access token
const response = await fetch('https://api.example.com/protected', {
headers: {
Authorization: Bearer ${accessToken},
},
});
return response.json();
}
// Step 4: Logout
async function logout() {
const accessToken = localStorage.getItem('access_token');
if (accessToken) {
await client.revokeToken(accessToken);
}
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('token_expires_at');
}
`
#### generateCodeVerifier(): string
Generates a cryptographically random code verifier (43-128 characters) for PKCE.
#### generateCodeChallenge(verifier: string, method?: 'plain' | 'S256'): Promise
Generates a code challenge from a verifier. Use S256 (SHA256) for security.
#### buildAuthorizeUrl(config: AuthorizeConfig): string
Builds the OAuth authorization URL using the Tekcify auth server.
#### exchangeCode(config: ExchangeCodeConfig): Promise
Exchanges an authorization code for access and refresh tokens.
#### refreshAccessToken(config: RefreshTokenConfig): Promise
Refreshes an expired access token using a refresh token.
#### revokeToken(config: RevokeTokenConfig): Promise
Revokes an access or refresh token.
#### introspectToken(config: IntrospectConfig): Promise
Checks if a token is valid and returns its metadata.
#### getUserInfo(accessToken: string): Promise
Retrieves user information using an access token.
`typescript
interface TokenResponse {
accessToken: string;
refreshToken: string;
tokenType: string; // Usually "Bearer"
expiresIn: number; // Seconds until expiration
scope: string; // Space-separated scopes
}
interface UserInfo {
sub: string; // User ID
email: string;
email_verified: boolean;
given_name: string | null;
family_name: string | null;
name: string | null;
picture: string | null;
updated_at: number; // Unix timestamp
}
interface IntrospectResult {
active: boolean;
clientId?: string;
username?: string;
scope?: string;
sub?: string;
exp?: number;
iat?: number;
tokenType?: string;
}
`
1. Always use PKCE: Required for public clients, recommended for all
2. Store tokens securely: Use httpOnly cookies or secure storage
3. Validate state parameter: Prevents CSRF attacks
4. Never expose client secrets: Only use in server-side code
5. Handle token expiration: Implement automatic refresh logic
6. Revoke tokens on logout: Prevents unauthorized access
This package uses modern Web APIs:
- crypto.getRandomValues() for random number generationcrypto.subtle.digest()
- for SHA256 hashingfetch()
- for HTTP requests
Supported in all modern browsers (Chrome, Firefox, Safari, Edge).
For Node.js environments, ensure you have:
- Node.js 18+ (for native fetch support)
- Or use a fetch polyfill like node-fetch`
MIT