@tekcify/auth-core-client
v2.0.0
Published
Core OAuth 2.0 client utilities for Tekcify Auth
Readme
@tekcify/auth-core-client
Core 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.).
Installation
npm install @tekcify/auth-core-client
# or
pnpm add @tekcify/auth-core-client
# or
yarn add @tekcify/auth-core-clientOverview
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_URLconstant
Configuration
The package uses a centralized auth server URL that can be imported and configured:
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.
Quick Start
1. Generate PKCE Parameters
PKCE is required for secure OAuth flows, especially for public clients (SPAs, mobile apps).
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);2. Build Authorization URL
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;3. Exchange Authorization Code for Tokens
After the user is redirected back with an authorization code:
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);4. Refresh Access Token
Access tokens expire. Use the refresh token to get a new one:
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);5. Get User Information
import { getUserInfo } from '@tekcify/auth-core-client';
const userInfo = await getUserInfo(accessToken);
console.log(userInfo.email); // [email protected]
console.log(userInfo.name); // John Doe
console.log(userInfo.email_verified); // trueUsing the OAuth Client Class
For a more object-oriented approach, use the OAuthClient class:
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');
}Complete OAuth Flow Example
Here's a complete example of implementing the OAuth flow:
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');
}API Reference
Functions
generateCodeVerifier(): string
Generates a cryptographically random code verifier (43-128 characters) for PKCE.
generateCodeChallenge(verifier: string, method?: 'plain' | 'S256'): Promise<string>
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<TokenResponse>
Exchanges an authorization code for access and refresh tokens.
refreshAccessToken(config: RefreshTokenConfig): Promise<TokenResponse>
Refreshes an expired access token using a refresh token.
revokeToken(config: RevokeTokenConfig): Promise<void>
Revokes an access or refresh token.
introspectToken(config: IntrospectConfig): Promise<IntrospectResult>
Checks if a token is valid and returns its metadata.
getUserInfo(accessToken: string): Promise<UserInfo>
Retrieves user information using an access token.
Types
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;
}Security Best Practices
- Always use PKCE: Required for public clients, recommended for all
- Store tokens securely: Use httpOnly cookies or secure storage
- Validate state parameter: Prevents CSRF attacks
- Never expose client secrets: Only use in server-side code
- Handle token expiration: Implement automatic refresh logic
- Revoke tokens on logout: Prevents unauthorized access
Browser Compatibility
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).
Node.js Usage
For Node.js environments, ensure you have:
- Node.js 18+ (for native fetch support)
- Or use a fetch polyfill like
node-fetch
License
MIT
