Skip to main content

Utility Functions

The SDK includes several utility functions for common OAuth and token management tasks.

PKCE Utilities

generatePKCEAsync()

Generate PKCE (Proof Key for Code Exchange) code verifier and challenge using SHA-256. Returns: Promise<PKCEPair>
import { generatePKCEAsync } from "ai-auth";

const pkce = await generatePKCEAsync();

console.log("Code Verifier:", pkce.codeVerifier);
console.log("Code Challenge:", pkce.codeChallenge);

// Use in OAuth flow
const params = new URLSearchParams({
  response_type: "code",
  client_id: "your-agent-id",
  redirect_uri: "http://localhost:3000/callback",
  code_challenge: pkce.codeChallenge,
  code_challenge_method: "S256",
});
Return Type:
interface PKCEPair {
  codeVerifier: string; // Random base64url-encoded string
  codeChallenge: string; // SHA-256 hash of verifier (base64url-encoded)
}
Details:
  • Generates a 32-byte random code verifier
  • Creates SHA-256 hash of the verifier as the code challenge
  • Uses base64url encoding (URL-safe)
  • Works in both browser and Node.js environments
  • OAuth 2.1 compliant
The AgentSDK.getAuthorizationUrl() method automatically generates PKCE parameters, so you typically don’t need to call this directly.

generateState()

Generate a random state parameter for CSRF protection in OAuth flows. Returns: string
import { generateState } from "ai-auth";

const state = generateState();

console.log("State:", state);
// Example: 'K7gNU3sdo-OL0wNhqoVWhr3g6s1xYv72ol_pe_Unols'

// Use in OAuth flow
const params = new URLSearchParams({
  response_type: "code",
  client_id: "your-agent-id",
  state: state,
  // ... other params
});
Details:
  • Generates a 16-byte random value
  • Base64url encoded (URL-safe)
  • Cryptographically secure random generation
  • Works in both browser and Node.js environments
Best Practices:
  • Always use a unique state parameter for each OAuth request
  • Store the state parameter and validate it in the callback
  • Never reuse state parameters
The AgentSDK.getAuthorizationUrl() method automatically generates a state parameter if not provided.

JWT Utilities

parseJWT()

Parse a JWT token to extract claims (without verification).
token
string
required
JWT token to parse
Returns: any - Parsed token claims, or null if invalid
import { parseJWT } from "ai-auth";

const idToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
const claims = parseJWT(idToken);

if (claims) {
  console.log("Subject:", claims.sub);
  console.log("Issued at:", claims.iat);
  console.log("Expires at:", claims.exp);
  console.log("Agent ID:", claims.agent_id);
  console.log("Email:", claims.email);
} else {
  console.error("Invalid JWT format");
}
Example Claims:
{
  sub: 'user-123',                    // Subject (user ID)
  iss: 'https://api.auth-agent.com',  // Issuer
  aud: 'my-agent-id',                 // Audience (agent ID)
  exp: 1735689600,                    // Expiration (Unix timestamp)
  iat: 1735686000,                    // Issued at (Unix timestamp)
  agent_id: 'my-agent',               // Agent identifier
  email: 'user@example.com',          // User email
  name: 'John Doe',                   // User name
  model_name: 'gpt-4'                 // AI model name
}
This function does NOT verify the JWT signature. Only use it for reading claims from tokens you trust. For security-critical operations, use server-side verification.
Use Cases:
  • Reading user information from ID tokens
  • Checking token expiration
  • Debugging token contents
  • Displaying user information
Security Notes:
  • Never trust claims from unverified tokens for authorization
  • Always verify tokens server-side for sensitive operations
  • Use this only for reading non-sensitive information

Token Expiration Utilities

isTokenExpired()

Check if a token is expired based on its expiration timestamp.
expiresAt
number
required
Expiration timestamp in milliseconds
Returns: boolean - true if expired, false if valid
import { isTokenExpired } from "ai-auth";

const expiresAt = Date.now() + 3600 * 1000; // Expires in 1 hour

if (isTokenExpired(expiresAt)) {
  console.log("Token is expired");
  // Refresh or re-authenticate
} else {
  console.log("Token is still valid");
  // Use the token
}
Example with Token Manager:
import { TokenManager, isTokenExpired } from "ai-auth";

const tokenManager = new TokenManager();

// ... set tokens ...

if (tokenManager.expiresAt && isTokenExpired(tokenManager.expiresAt)) {
  console.log("Need to refresh token");
}
The TokenManager.isExpired() method uses this utility internally.

getTimeUntilExpiry()

Get the time remaining until a token expires.
expiresAt
number
required
Expiration timestamp in milliseconds
Returns: number - Milliseconds until expiration (minimum 0)
import { getTimeUntilExpiry } from "ai-auth";

const expiresAt = Date.now() + 3600 * 1000; // Expires in 1 hour

const timeLeft = getTimeUntilExpiry(expiresAt);
const secondsLeft = Math.floor(timeLeft / 1000);
const minutesLeft = Math.floor(secondsLeft / 60);

console.log(`Token expires in ${minutesLeft} minutes`);

// Schedule refresh before expiration
if (secondsLeft < 300) {
  // Less than 5 minutes
  console.log("Token expires soon, refreshing...");
  await sdk.refreshAccessToken(refreshToken);
}
Practical Examples: Display countdown:
function displayTokenExpiry(expiresAt: number) {
  const timeLeft = getTimeUntilExpiry(expiresAt);

  if (timeLeft === 0) {
    return "Expired";
  }

  const seconds = Math.floor(timeLeft / 1000);
  const minutes = Math.floor(seconds / 60);
  const hours = Math.floor(minutes / 60);

  if (hours > 0) {
    return `${hours}h ${minutes % 60}m`;
  } else if (minutes > 0) {
    return `${minutes}m ${seconds % 60}s`;
  } else {
    return `${seconds}s`;
  }
}

console.log(displayTokenExpiry(tokenManager.expiresAt!));
Auto-refresh before expiration:
import { getTimeUntilExpiry, sleep } from "ai-auth";

async function autoRefreshToken(sdk: AgentSDK, tokenManager: TokenManager) {
  while (true) {
    if (tokenManager.expiresAt) {
      const timeLeft = getTimeUntilExpiry(tokenManager.expiresAt);
      const refreshBuffer = 5 * 60 * 1000; // 5 minutes

      if (timeLeft < refreshBuffer && tokenManager.refreshToken) {
        console.log("Auto-refreshing token...");
        await sdk.refreshAccessToken(tokenManager.refreshToken);
      }
    }

    await sleep(60000); // Check every minute
  }
}

Helper Utilities

sleep()

Pause execution for a specified duration.
ms
number
required
Duration to sleep in milliseconds
Returns: Promise<void>
import { sleep } from "ai-auth";

console.log("Starting...");
await sleep(2000); // Sleep for 2 seconds
console.log("Done!");
Use Cases: Polling with delays:
async function pollForChallenge(verifyCtxId: string) {
  const maxAttempts = 10;

  for (let i = 0; i < maxAttempts; i++) {
    const challenge = await fetchChallenge(verifyCtxId);

    if (challenge) {
      return challenge;
    }

    console.log(`Attempt ${i + 1}/${maxAttempts}, waiting...`);
    await sleep(2000); // Wait 2 seconds between attempts
  }

  throw new Error("Polling timeout");
}
Rate limiting:
async function makeRequestsWithDelay(requests: string[]) {
  for (const request of requests) {
    await processRequest(request);
    await sleep(1000); // 1 second delay between requests
  }
}
Retry with backoff:
async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;

      const delay = Math.pow(2, i) * 1000; // Exponential backoff
      console.log(`Retry ${i + 1}/${maxRetries} in ${delay}ms...`);
      await sleep(delay);
    }
  }

  throw new Error("Max retries exceeded");
}

// Usage
const result = await retryWithBackoff(() => sdk.getUserInfo(accessToken));

Complete Examples

Example 1: Manual OAuth Flow with PKCE

import { generatePKCEAsync, generateState, parseJWT } from "ai-auth";

// Step 1: Generate PKCE parameters and state
const pkce = await generatePKCEAsync();
const state = generateState();

// Store for later
await storage.save({
  codeVerifier: pkce.codeVerifier,
  state: state,
});

// Step 2: Build authorization URL
const params = new URLSearchParams({
  response_type: "code",
  client_id: "my-agent-id",
  redirect_uri: "http://localhost:3000/callback",
  scope: "openid profile email agent",
  state: state,
  code_challenge: pkce.codeChallenge,
  code_challenge_method: "S256",
});

const authUrl = `https://api.auth-agent.com/oauth2/authorize?${params}`;
console.log("Visit:", authUrl);

// Step 3: In callback, exchange code for tokens
const { codeVerifier, state: savedState } = await storage.get();

if (callbackState !== savedState) {
  throw new Error("State mismatch - possible CSRF attack");
}

const tokenResponse = await fetch("https://api.auth-agent.com/oauth2/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    code: authCode,
    redirect_uri: "http://localhost:3000/callback",
    client_id: "my-agent-id",
    code_verifier: codeVerifier,
  }),
});

const tokens = await tokenResponse.json();

// Step 4: Parse ID token
const claims = parseJWT(tokens.id_token);
console.log("User:", claims.email);
console.log("Expires at:", new Date(claims.exp * 1000));

Example 2: Token Expiration Monitoring

import { isTokenExpired, getTimeUntilExpiry, sleep } from "ai-auth";

async function monitorTokenExpiration(
  sdk: AgentSDK,
  tokenManager: TokenManager
) {
  setInterval(() => {
    if (!tokenManager.expiresAt) {
      console.log("No token stored");
      return;
    }

    if (isTokenExpired(tokenManager.expiresAt)) {
      console.log("Token expired!");
      return;
    }

    const timeLeft = getTimeUntilExpiry(tokenManager.expiresAt);
    const minutes = Math.floor(timeLeft / 60000);

    console.log(`Token expires in ${minutes} minutes`);

    // Auto-refresh if less than 5 minutes left
    if (minutes < 5 && tokenManager.refreshToken) {
      console.log("Auto-refreshing token...");
      sdk
        .refreshAccessToken(tokenManager.refreshToken)
        .then(() => console.log("Token refreshed"))
        .catch((err) => console.error("Refresh failed:", err));
    }
  }, 60000); // Check every minute
}

Example 3: JWT Claims Parsing

import { parseJWT } from "ai-auth";

function displayUserInfo(idToken: string) {
  const claims = parseJWT(idToken);

  if (!claims) {
    console.error("Invalid JWT token");
    return;
  }

  console.log("=== User Information ===");
  console.log("User ID:", claims.sub);
  console.log("Email:", claims.email);
  console.log("Name:", claims.name);
  console.log("Agent ID:", claims.agent_id);
  console.log("Model:", claims.model_name);

  // Check expiration
  const expiresAt = claims.exp * 1000; // Convert to milliseconds
  const now = Date.now();

  if (expiresAt < now) {
    console.log("Token Status: EXPIRED");
  } else {
    const minutesLeft = Math.floor((expiresAt - now) / 60000);
    console.log(`Token Status: Valid for ${minutesLeft} more minutes`);
  }

  // Display issued time
  const issuedAt = new Date(claims.iat * 1000);
  console.log("Issued at:", issuedAt.toLocaleString());

  // Display expiration time
  const expiresAtDate = new Date(expiresAt);
  console.log("Expires at:", expiresAtDate.toLocaleString());
}

// Usage
const tokens = await sdk.exchangeCode(code, state, redirectUri);
displayUserInfo(tokens.id_token!);

Example 4: Polling with Sleep

import { sleep } from "ai-auth";

async function pollForAuthentication(
  sdk: AgentSDK,
  verifyCtxId: string,
  options = { timeout: 60000, interval: 2000 }
): Promise<boolean> {
  const startTime = Date.now();

  while (Date.now() - startTime < options.timeout) {
    try {
      const challenge = await sdk.requestChallenge(accessToken);

      if (challenge.challenge) {
        // Challenge available, confirm it
        await sdk.confirmVerification(accessToken, {
          challenge: challenge.challenge,
          expires: challenge.expires!,
          sig: challenge.sig!,
          verify_ctx_id: verifyCtxId,
        });

        return true;
      }

      // No challenge yet, wait and retry
      await sleep(options.interval);
    } catch (error) {
      console.error("Polling error:", error);
      await sleep(options.interval);
    }
  }

  console.error("Polling timeout exceeded");
  return false;
}

// Usage
const success = await pollForAuthentication(sdk, "verify-123");
console.log("Authentication:", success ? "SUCCESS" : "FAILED");

Example 5: Comprehensive Token Management

import {
  generatePKCEAsync,
  generateState,
  parseJWT,
  isTokenExpired,
  getTimeUntilExpiry,
  sleep,
} from "ai-auth";

class TokenService {
  private tokens: any = null;
  private expiresAt: number = 0;

  async startOAuthFlow() {
    const pkce = await generatePKCEAsync();
    const state = generateState();

    await this.storage.save({ pkce, state });

    return {
      url: this.buildAuthUrl(pkce.codeChallenge, state),
      state,
    };
  }

  async handleCallback(code: string, state: string) {
    const { pkce, state: savedState } = await this.storage.get();

    if (state !== savedState) {
      throw new Error("State mismatch");
    }

    this.tokens = await this.exchangeCode(code, pkce.codeVerifier);
    this.expiresAt = Date.now() + this.tokens.expires_in * 1000;

    // Parse ID token
    const claims = parseJWT(this.tokens.id_token);
    console.log("Logged in as:", claims.email);

    return this.tokens;
  }

  async getValidAccessToken(): Promise<string> {
    if (!this.tokens) {
      throw new Error("Not authenticated");
    }

    if (isTokenExpired(this.expiresAt)) {
      console.log("Token expired, refreshing...");
      await this.refreshTokens();
    } else {
      const timeLeft = getTimeUntilExpiry(this.expiresAt);
      console.log(`Token valid for ${Math.floor(timeLeft / 1000)}s`);
    }

    return this.tokens.access_token;
  }

  private async refreshTokens() {
    if (!this.tokens.refresh_token) {
      throw new Error("No refresh token available");
    }

    // Refresh implementation
    // ...
  }
}

Best Practices

const state = generateState();
await storage.save({ state });

// In callback
if (callbackState !== savedState) {
  throw new Error('State mismatch - CSRF attack detected');
}
// ❌ Don't use for authorization decisions
const claims = parseJWT(token);
if (claims.role === 'admin') {
  grantAdminAccess(); // UNSAFE!
}

// ✅ Only use for display/debugging
const claims = parseJWT(idToken);
console.log('User email:', claims.email);
if (isTokenExpired(expiresAt)) {
  await refreshOrReauthenticate();
}
const token = tokenManager.getAccessToken();
// Good: Reasonable interval
await sleep(2000); // 2 seconds

// Bad: Too aggressive
await sleep(100); // 100ms - may overload server

// Bad: Too slow
await sleep(30000); // 30s - poor UX

Next Steps