Skip to main content

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:
  1. Generate Code Verifier: Create a cryptographically random string
  2. Generate Code Challenge: Hash the code verifier with SHA-256
  3. Authorization Request: Send code challenge to authorization server
  4. 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

verifier
string
required
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

baseUrl
string
required
The base authorization endpoint URL (e.g., “https://provider.com/oauth/authorize”)
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

tokenUrl
string
required
The token endpoint URL (e.g., “https://provider.com/oauth/token”)
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
}

Build docs developers (and LLMs) love