OAuth Utilities
Utility functions for implementing OAuth 2.0 authorization flows with PKCE (Proof Key for Code Exchange) support.
PKCE Flow Overview
PKCE (Proof Key for Code Exchange) is a security extension to OAuth 2.0 that prevents authorization code interception attacks. The flow works as follows:
- Generate Code Verifier: Create a cryptographically random string
- Generate Code Challenge: Hash the code verifier with SHA-256
- Authorization Request: Send code challenge to authorization server
- Token Exchange: Send code verifier to prove you initiated the flow
PKCE is especially important for public clients (mobile apps, SPAs) that cannot securely store client secrets.
generateCodeVerifier
Generates a cryptographically secure code verifier for PKCE flow.
async function generateCodeVerifier(): Promise<string>
Returns
Returns a Promise that resolves to a base64url-encoded string (43-128 characters) suitable for use as an OAuth 2.0 PKCE code verifier.
Examples
import { generateCodeVerifier } from "@arraf-auth/core"
// Generate code verifier
const verifier = await generateCodeVerifier()
// "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
// Store in session or secure storage
sessionStorage.setItem("code_verifier", verifier)
Store the code verifier securely (in session storage or secure cookie) as you’ll need it to exchange the authorization code for tokens.
generateCodeChallenge
Generates the code challenge from a code verifier using SHA-256 hashing.
async function generateCodeChallenge(verifier: string): Promise<string>
Parameters
The code verifier string generated by generateCodeVerifier()
Returns
Returns a Promise that resolves to a base64url-encoded SHA-256 hash of the code verifier.
Examples
import { generateCodeVerifier, generateCodeChallenge } from "@arraf-auth/core"
// Generate verifier and challenge
const verifier = await generateCodeVerifier()
const challenge = await generateCodeChallenge(verifier)
// Store verifier for later use
sessionStorage.setItem("code_verifier", verifier)
// Use challenge in authorization URL
const authUrl = buildAuthorizationUrl(
"https://provider.com/oauth/authorize",
{
client_id: "your-client-id",
code_challenge: challenge,
code_challenge_method: "S256",
// ... other params
}
)
The code challenge is derived from the verifier using SHA-256 hashing. This allows the authorization server to verify that the client exchanging the code is the same client that initiated the authorization request.
generateState
Generates a random state parameter for CSRF protection in OAuth flows.
function generateState(): string
Returns
Returns a cryptographically secure random string suitable for use as an OAuth state parameter.
Examples
import { generateState } from "@arraf-auth/core"
// Generate state parameter
const state = generateState()
// "5ca75bd30"
// Store state to validate callback
sessionStorage.setItem("oauth_state", state)
// Include in authorization URL
const authUrl = buildAuthorizationUrl(
"https://provider.com/oauth/authorize",
{
client_id: "your-client-id",
state: state,
// ... other params
}
)
// Later, validate in callback
const callbackState = new URLSearchParams(window.location.search).get("state")
const storedState = sessionStorage.getItem("oauth_state")
if (callbackState !== storedState) {
throw new Error("State mismatch - possible CSRF attack")
}
Always validate the state parameter in your OAuth callback to prevent CSRF attacks. The state returned by the provider must match the state you sent.
buildAuthorizationUrl
Builds a complete OAuth authorization URL with query parameters.
function buildAuthorizationUrl(
baseUrl: string,
params: Record<string, string>
): string
Parameters
params
Record<string, string>
required
Object containing OAuth parameters as key-value pairs
Returns
Returns the complete authorization URL with properly encoded query parameters.
Examples
import { buildAuthorizationUrl, generateState, generateCodeVerifier, generateCodeChallenge } from "@arraf-auth/core"
// Complete PKCE flow setup
const verifier = await generateCodeVerifier()
const challenge = await generateCodeChallenge(verifier)
const state = generateState()
// Build authorization URL
const authUrl = buildAuthorizationUrl(
"https://accounts.google.com/o/oauth2/v2/auth",
{
client_id: "your-client-id.apps.googleusercontent.com",
redirect_uri: "https://yourapp.com/callback",
response_type: "code",
scope: "openid profile email",
state: state,
code_challenge: challenge,
code_challenge_method: "S256",
}
)
// Redirect user to authorization URL
window.location.href = authUrl
// https://accounts.google.com/o/oauth2/v2/auth?client_id=...&code_challenge=...
The function properly URL-encodes all parameters, so you can safely pass any string values including special characters.
exchangeCodeForTokens
Exchanges an authorization code for access and refresh tokens.
async function exchangeCodeForTokens(
tokenUrl: string,
params: Record<string, string>
): Promise<Response>
Parameters
params
Record<string, string>
required
Object containing token exchange parameters including:
code: Authorization code from callback
client_id: Your OAuth client ID
redirect_uri: Must match the redirect URI used in authorization
code_verifier: The code verifier generated earlier (for PKCE)
grant_type: Usually “authorization_code”
Returns
Returns a Promise that resolves to the fetch Response object. You need to parse the response JSON to get the tokens.
Examples
import { exchangeCodeForTokens } from "@arraf-auth/core"
// In your OAuth callback handler
const urlParams = new URLSearchParams(window.location.search)
const code = urlParams.get("code")
const state = urlParams.get("state")
// Validate state
const storedState = sessionStorage.getItem("oauth_state")
if (state !== storedState) {
throw new Error("State mismatch")
}
// Get stored code verifier
const codeVerifier = sessionStorage.getItem("code_verifier")
// Exchange code for tokens
const response = await exchangeCodeForTokens(
"https://oauth2.googleapis.com/token",
{
code: code,
client_id: "your-client-id.apps.googleusercontent.com",
redirect_uri: "https://yourapp.com/callback",
grant_type: "authorization_code",
code_verifier: codeVerifier,
}
)
if (!response.ok) {
const error = await response.json()
throw new Error(`Token exchange failed: ${error.error_description}`)
}
const tokens = await response.json()
// {
// access_token: "ya29.a0AfH6SMBx...",
// refresh_token: "1//0gZ3...",
// token_type: "Bearer",
// expires_in: 3600,
// scope: "openid profile email"
// }
// Store tokens securely
// Use access_token for API requests
The function sends a POST request with application/x-www-form-urlencoded content type, which is the standard format for OAuth token exchange. Always check the response status before parsing the JSON.
Complete PKCE Flow Example
Here’s a complete example of implementing OAuth with PKCE:
import {
generateCodeVerifier,
generateCodeChallenge,
generateState,
buildAuthorizationUrl,
exchangeCodeForTokens
} from "@arraf-auth/core"
// Step 1: Initiate OAuth flow
async function initiateOAuth() {
const verifier = await generateCodeVerifier()
const challenge = await generateCodeChallenge(verifier)
const state = generateState()
// Store for callback
sessionStorage.setItem("code_verifier", verifier)
sessionStorage.setItem("oauth_state", state)
// Redirect to authorization
const authUrl = buildAuthorizationUrl(
"https://provider.com/oauth/authorize",
{
client_id: "your-client-id",
redirect_uri: "https://yourapp.com/callback",
response_type: "code",
scope: "openid profile email",
state: state,
code_challenge: challenge,
code_challenge_method: "S256",
}
)
window.location.href = authUrl
}
// Step 2: Handle OAuth callback
async function handleCallback() {
const params = new URLSearchParams(window.location.search)
const code = params.get("code")
const state = params.get("state")
// Validate state
const storedState = sessionStorage.getItem("oauth_state")
if (state !== storedState) {
throw new Error("Invalid state parameter")
}
// Exchange code for tokens
const codeVerifier = sessionStorage.getItem("code_verifier")
const response = await exchangeCodeForTokens(
"https://provider.com/oauth/token",
{
code: code,
client_id: "your-client-id",
redirect_uri: "https://yourapp.com/callback",
grant_type: "authorization_code",
code_verifier: codeVerifier,
}
)
const tokens = await response.json()
// Clean up stored values
sessionStorage.removeItem("code_verifier")
sessionStorage.removeItem("oauth_state")
return tokens
}