Skip to main content

TokenManager

The TokenManager class provides a simple, built-in solution for managing OAuth tokens with automatic expiration tracking.

Overview

The TokenManager handles:
  • Storing access tokens, refresh tokens, and ID tokens
  • Tracking token expiration with a 60-second buffer
  • Automatic expiration checking
  • Token lifecycle management
The TokenManager stores tokens in memory. For production use with persistent storage, implement custom token storage using the SDK’s callbacks (onTokensReceived, onTokensRefreshed, onTokensRevoked).

Usage

Basic Setup

import { AgentSDK, TokenManager } from "ai-auth";

const tokenManager = new TokenManager();

const sdk = new AgentSDK({
  agentId: "your-agent-id",
  onTokensReceived: (tokens) => tokenManager.setTokens(tokens),
  onTokensRefreshed: (tokens) => tokenManager.setTokens(tokens),
  onTokensRevoked: () => tokenManager.clear(),
});

Complete Flow

import { AgentSDK, TokenManager } from "ai-auth";

const tokenManager = new TokenManager();
const sdk = new AgentSDK({
  agentId: "your-agent-id",
  onTokensReceived: (tokens) => tokenManager.setTokens(tokens),
  onTokensRefreshed: (tokens) => tokenManager.setTokens(tokens),
  onTokensRevoked: () => tokenManager.clear(),
});

// OAuth flow
const { url } = await sdk.getAuthorizationUrl({
  redirectUri: "http://localhost:3000/callback",
});

// After callback
const tokens = await sdk.exchangeCode(code, state, redirectUri);
// tokens automatically stored via onTokensReceived

// Check if token is expired
if (tokenManager.isExpired()) {
  // Refresh if available
  if (tokenManager.refreshToken) {
    await sdk.refreshAccessToken(tokenManager.refreshToken);
  } else {
    // Re-authenticate
    redirectToLogin();
  }
}

// Get access token
const accessToken = tokenManager.getAccessToken();

// Make authenticated requests
const userInfo = await sdk.getUserInfo(accessToken);

Properties

accessToken

accessToken
string | null
The current access token. null if no token is stored.
const token = tokenManager.accessToken;
if (token) {
  console.log("Access token available");
}

idToken

idToken
string | null
The current OpenID Connect ID token. null if not available.
const idToken = tokenManager.idToken;
if (idToken) {
  // Parse ID token claims
  const claims = parseJWT(idToken);
  console.log("User ID:", claims.sub);
}

refreshToken

refreshToken
string | null
The current refresh token. null if not available.
if (tokenManager.refreshToken) {
  // Refresh token available - can refresh access token
  await sdk.refreshAccessToken(tokenManager.refreshToken);
} else {
  // No refresh token - need to re-authenticate
  redirectToLogin();
}

tokenType

tokenType
string
default:"Bearer"
The token type. Always 'Bearer' for OAuth 2.1.
console.log(tokenManager.tokenType); // 'Bearer'

expiresAt

expiresAt
number | null
Timestamp (in milliseconds) when the access token expires. Includes a 60-second buffer. null if no token is stored.
if (tokenManager.expiresAt) {
  const timeLeft = tokenManager.expiresAt - Date.now();
  console.log(`Token expires in ${Math.floor(timeLeft / 1000)} seconds`);
}

scopes

scopes
string | null
Space-separated list of granted scopes. null if no token is stored.
console.log("Granted scopes:", tokenManager.scopes);
// Example: 'openid profile email agent'

Methods

setTokens()

Store tokens from an OAuth response.
tokenResponse
TokenResponse
required
OAuth token response object
Returns: void
const tokens = await sdk.exchangeCode(code, state, redirectUri);
tokenManager.setTokens(tokens);

console.log("Access token:", tokenManager.accessToken);
console.log("Refresh token:", tokenManager.refreshToken);
console.log("ID token:", tokenManager.idToken);
What it does:
  • Stores access token, refresh token, and ID token
  • Calculates expiration time with a 60-second buffer
  • Stores token type and scopes
Token Response Format:
{
  access_token: string;
  token_type: 'Bearer';
  expires_in: number;        // Seconds until expiration
  refresh_token?: string;
  id_token?: string;
  scope: string;
}

isExpired()

Check if the access token is expired. Returns: boolean - true if expired or no token, false if valid
if (tokenManager.isExpired()) {
  console.log("Token is expired, need to refresh");

  if (tokenManager.refreshToken) {
    await sdk.refreshAccessToken(tokenManager.refreshToken);
  } else {
    redirectToLogin();
  }
} else {
  console.log("Token is still valid");
  const accessToken = tokenManager.getAccessToken();
}
Expiration Logic:
  • Returns true if no token is stored
  • Returns true if no expiration time is set
  • Returns true if current time >= expiration time
  • Returns false if token is still valid
The expiration check includes a 60-second buffer to prevent using tokens that are about to expire.

getAccessToken()

Get the current access token. Throws an error if the token is expired. Returns: string - The access token Throws: Error if token is expired
try {
  const accessToken = tokenManager.getAccessToken();

  // Use the token
  const userInfo = await sdk.getUserInfo(accessToken);
} catch (error) {
  console.error("Token expired, refreshing...");

  if (tokenManager.refreshToken) {
    await sdk.refreshAccessToken(tokenManager.refreshToken);
    const newToken = tokenManager.getAccessToken();
  }
}
Best Practice: Always check isExpired() before calling getAccessToken() to avoid exceptions:
if (tokenManager.isExpired()) {
  // Refresh or re-authenticate
  if (tokenManager.refreshToken) {
    await sdk.refreshAccessToken(tokenManager.refreshToken);
  } else {
    return redirectToLogin();
  }
}

const accessToken = tokenManager.getAccessToken();

clear()

Clear all stored tokens and reset the manager. Returns: void
// Logout
await sdk.revokeToken(tokenManager.accessToken!);
tokenManager.clear();

console.log(tokenManager.accessToken); // null
console.log(tokenManager.refreshToken); // null
console.log(tokenManager.idToken); // null
console.log(tokenManager.expiresAt); // null
What it clears:
  • accessToken
  • idToken
  • refreshToken
  • expiresAt
  • scopes

Complete Examples

Example 1: Basic Usage

import { AgentSDK, TokenManager } from "ai-auth";

const tokenManager = new TokenManager();

const sdk = new AgentSDK({
  agentId: "my-agent",
  onTokensReceived: (tokens) => tokenManager.setTokens(tokens),
  onTokensRefreshed: (tokens) => tokenManager.setTokens(tokens),
  onTokensRevoked: () => tokenManager.clear(),
});

// Start OAuth flow
const { url, codeVerifier, state } = await sdk.getAuthorizationUrl({
  redirectUri: "http://localhost:3000/callback",
});

console.log("Visit:", url);

// Handle callback
const tokens = await sdk.exchangeCode(
  code,
  state,
  "http://localhost:3000/callback"
);

// Make authenticated request
if (!tokenManager.isExpired()) {
  const accessToken = tokenManager.getAccessToken();
  const profile = await sdk.getAgentProfile(accessToken);
  console.log(profile);
}

Example 2: Automatic Token Refresh

import { AgentSDK, TokenManager } from "ai-auth";

const tokenManager = new TokenManager();

const sdk = new AgentSDK({
  agentId: "my-agent",
  onTokensReceived: (tokens) => tokenManager.setTokens(tokens),
  onTokensRefreshed: (tokens) => tokenManager.setTokens(tokens),
  onTokensRevoked: () => tokenManager.clear(),
});

async function getValidAccessToken(): Promise<string> {
  // Check if token is expired
  if (tokenManager.isExpired()) {
    if (tokenManager.refreshToken) {
      // Refresh the token
      console.log("Refreshing access token...");
      await sdk.refreshAccessToken(tokenManager.refreshToken);
    } else {
      throw new Error("No refresh token available, re-authentication required");
    }
  }

  return tokenManager.getAccessToken();
}

// Use the helper
try {
  const accessToken = await getValidAccessToken();
  const userInfo = await sdk.getUserInfo(accessToken);
  console.log(userInfo);
} catch (error) {
  console.error("Authentication failed:", error);
  // Redirect to login
}

Example 3: Token Lifecycle Management

import { AgentSDK, TokenManager } from "ai-auth";

const tokenManager = new TokenManager();

const sdk = new AgentSDK({
  agentId: "my-agent",
  onTokensReceived: (tokens) => {
    console.log("Received new tokens");
    tokenManager.setTokens(tokens);
    displayTokenInfo();
  },
  onTokensRefreshed: (tokens) => {
    console.log("Tokens refreshed");
    tokenManager.setTokens(tokens);
    displayTokenInfo();
  },
  onTokensRevoked: () => {
    console.log("Tokens revoked");
    tokenManager.clear();
  },
});

function displayTokenInfo() {
  if (tokenManager.accessToken) {
    const timeLeft = tokenManager.expiresAt! - Date.now();
    console.log(`Token expires in ${Math.floor(timeLeft / 1000)} seconds`);
    console.log(`Scopes: ${tokenManager.scopes}`);
    console.log(`Has refresh token: ${!!tokenManager.refreshToken}`);
  }
}

// Login
const { url } = await sdk.getAuthorizationUrl({
  redirectUri: "http://localhost:3000/callback",
});

// After callback
await sdk.exchangeCode(code, state, redirectUri);

// Logout
async function logout() {
  if (tokenManager.accessToken) {
    await sdk.revokeToken(tokenManager.accessToken);
    // tokenManager.clear() is automatically called via onTokensRevoked
  }
}

Example 4: Monitoring Token Expiration

import { AgentSDK, TokenManager } from "ai-auth";

const tokenManager = new TokenManager();

const sdk = new AgentSDK({
  agentId: "my-agent",
  onTokensReceived: (tokens) => tokenManager.setTokens(tokens),
  onTokensRefreshed: (tokens) => tokenManager.setTokens(tokens),
  onTokensRevoked: () => tokenManager.clear(),
});

// Check token status periodically
setInterval(() => {
  if (tokenManager.accessToken) {
    if (tokenManager.expiresAt) {
      const timeLeft = tokenManager.expiresAt - Date.now();
      const secondsLeft = Math.floor(timeLeft / 1000);

      if (secondsLeft < 300) {
        // Less than 5 minutes
        console.warn(`Token expires soon: ${secondsLeft} seconds left`);

        if (tokenManager.refreshToken) {
          sdk
            .refreshAccessToken(tokenManager.refreshToken)
            .then(() => console.log("Token refreshed preemptively"))
            .catch((err) => console.error("Refresh failed:", err));
        }
      } else {
        console.log(`Token valid for ${secondsLeft} more seconds`);
      }
    }
  }
}, 60000); // Check every minute

When to Use TokenManager vs Custom Storage

Use TokenManager when:

  • Building a simple CLI tool or script
  • Prototyping or testing
  • Single-user, single-session applications
  • Short-lived processes

Use Custom Storage when:

  • Building production web applications
  • Supporting multiple users
  • Need persistent storage across sessions
  • Require encrypted token storage
  • Need distributed session management

Example: Custom Storage Implementation

import { AgentSDK } from "ai-auth";
import Database from "./database";

const db = new Database();

const sdk = new AgentSDK({
  agentId: "my-agent",
  onTokensReceived: async (tokens) => {
    await db.tokens.create({
      userId: currentUser.id,
      accessToken: tokens.access_token,
      refreshToken: tokens.refresh_token,
      idToken: tokens.id_token,
      expiresAt: Date.now() + tokens.expires_in * 1000,
      scope: tokens.scope,
    });
  },
  onTokensRefreshed: async (tokens) => {
    await db.tokens.update({
      where: { userId: currentUser.id },
      data: {
        accessToken: tokens.access_token,
        refreshToken: tokens.refresh_token,
        expiresAt: Date.now() + tokens.expires_in * 1000,
      },
    });
  },
  onTokensRevoked: async () => {
    await db.tokens.delete({
      where: { userId: currentUser.id },
    });
  },
});

Best Practices

if (tokenManager.isExpired()) {
  await refreshOrReauthenticate();
}
const token = tokenManager.getAccessToken();
if (tokenManager.isExpired()) {
  if (!tokenManager.refreshToken) {
    // No refresh token - need to re-authenticate
    return redirectToLogin();
  }
  await sdk.refreshAccessToken(tokenManager.refreshToken);
}
try {
  const token = tokenManager.getAccessToken();
} catch (error) {
  // Handle expired token
  await handleExpiredToken();
}
async function logout() {
  if (tokenManager.accessToken) {
    await sdk.revokeToken(tokenManager.accessToken);
  }
  tokenManager.clear();
}

Next Steps