CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-openid-client

OAuth 2.0 and OpenID Connect client library for JavaScript runtimes with comprehensive authentication flows and security features.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

token-management.mddocs/

Token Management

Token introspection, revocation, and lifecycle management operations for OAuth 2.0 access and refresh tokens.

Capabilities

Token Introspection

Query token status and metadata using RFC 7662 token introspection.

/**
 * Introspect token status and metadata
 * @param config - Configuration instance
 * @param token - Token to introspect (access or refresh token)
 * @param parameters - Additional introspection parameters
 * @returns Promise resolving to introspection response
 */
function tokenIntrospection(
  config: Configuration,
  token: string,
  parameters?: URLSearchParams | Record<string, string>
): Promise<IntrospectionResponse>;

Usage Examples:

import * as client from "openid-client";

// Basic token introspection
const introspection = await client.tokenIntrospection(config, accessToken);

if (introspection.active) {
  console.log("Token is active");
  console.log("Scope:", introspection.scope);
  console.log("Client ID:", introspection.client_id);
  console.log("Username:", introspection.username);
  console.log("Expires at:", new Date(introspection.exp * 1000));
} else {
  console.log("Token is inactive");
}

// With token type hint for better performance
const introspection = await client.tokenIntrospection(
  config,
  accessToken,
  {
    token_type_hint: "access_token"
  }
);

// Introspect refresh token
const refreshIntrospection = await client.tokenIntrospection(
  config,
  refreshToken,
  {
    token_type_hint: "refresh_token"
  }
);

// Check specific token properties
if (introspection.active) {
  // Check if token has required scope
  const hasScope = introspection.scope?.includes("read:users");
  
  // Check token expiration
  const expiresIn = introspection.exp - Math.floor(Date.now() / 1000);
  if (expiresIn < 300) {
    console.log("Token expires in less than 5 minutes");
  }
  
  // Check audience
  if (Array.isArray(introspection.aud)) {
    console.log("Token audiences:", introspection.aud);
  } else if (introspection.aud) {
    console.log("Token audience:", introspection.aud);
  }
}

Token Revocation

Revoke access or refresh tokens using RFC 7009 token revocation.

/**
 * Revoke token
 * @param config - Configuration instance
 * @param token - Token to revoke (access or refresh token)
 * @param parameters - Additional revocation parameters
 * @returns Promise resolving when revocation is complete
 */
function tokenRevocation(
  config: Configuration,
  token: string,
  parameters?: URLSearchParams | Record<string, string>
): Promise<void>;

Usage Examples:

import * as client from "openid-client";

// Basic token revocation
await client.tokenRevocation(config, accessToken);
console.log("Access token revoked");

// With token type hint
await client.tokenRevocation(config, refreshToken, {
  token_type_hint: "refresh_token"
});
console.log("Refresh token revoked");

// Revoke all tokens (if supported by server)
// Revoking refresh token may invalidate associated access tokens
await client.tokenRevocation(config, refreshToken, {
  token_type_hint: "refresh_token"
});

// Error handling
try {
  await client.tokenRevocation(config, token);
} catch (error) {
  if (error.error === "unsupported_token_type") {
    console.log("Server doesn't support revoking this token type");
  } else if (error.error === "invalid_request") {
    console.log("Invalid revocation request");
  } else {
    console.log("Revocation failed:", error.message);
  }
}

Token Lifecycle Management

Complete token lifecycle management patterns:

Token Storage and Refresh:

import * as client from "openid-client";

interface TokenStorage {
  accessToken?: string;
  refreshToken?: string;
  expiresAt?: number;
  idToken?: string;
}

class TokenManager {
  private tokens: TokenStorage = {};
  
  constructor(private config: client.Configuration) {}
  
  async getValidAccessToken(): Promise<string> {
    // Check if current token is valid
    if (this.tokens.accessToken && this.tokens.expiresAt) {
      const now = Math.floor(Date.now() / 1000);
      const bufferTime = 300; // 5 minutes buffer
      
      if (this.tokens.expiresAt > now + bufferTime) {
        return this.tokens.accessToken;
      }
    }
    
    // Refresh if needed
    if (this.tokens.refreshToken) {
      await this.refreshTokens();
      return this.tokens.accessToken!;
    }
    
    throw new Error("No valid tokens available");
  }
  
  async refreshTokens(): Promise<void> {
    if (!this.tokens.refreshToken) {
      throw new Error("No refresh token available");
    }
    
    try {
      const newTokens = await client.refreshTokenGrant(
        this.config,
        this.tokens.refreshToken
      );
      
      this.storeTokens(newTokens);
    } catch (error) {
      // Refresh failed - tokens may be expired
      this.clearTokens();
      throw error;
    }
  }
  
  storeTokens(tokens: client.TokenEndpointResponse & client.TokenEndpointResponseHelpers): void {
    this.tokens.accessToken = tokens.access_token;
    this.tokens.refreshToken = tokens.refresh_token || this.tokens.refreshToken;
    this.tokens.idToken = tokens.id_token;
    
    if (tokens.expires_in) {
      this.tokens.expiresAt = Math.floor(Date.now() / 1000) + tokens.expires_in;
    }
  }
  
  async revokeTokens(): Promise<void> {
    const promises: Promise<void>[] = [];
    
    if (this.tokens.accessToken) {
      promises.push(
        client.tokenRevocation(this.config, this.tokens.accessToken, {
          token_type_hint: "access_token"
        })
      );
    }
    
    if (this.tokens.refreshToken) {
      promises.push(
        client.tokenRevocation(this.config, this.tokens.refreshToken, {
          token_type_hint: "refresh_token"
        })
      );
    }
    
    await Promise.allSettled(promises);
    this.clearTokens();
  }
  
  clearTokens(): void {
    this.tokens = {};
  }
  
  async introspectToken(token?: string): Promise<client.IntrospectionResponse> {
    const tokenToIntrospect = token || this.tokens.accessToken;
    if (!tokenToIntrospect) {
      throw new Error("No token to introspect");
    }
    
    return client.tokenIntrospection(this.config, tokenToIntrospect);
  }
}

Response Types

interface IntrospectionResponse {
  /** Whether the token is currently active */
  active: boolean;
  
  /** Space-separated list of scopes */
  scope?: string;
  
  /** Client identifier */
  client_id?: string;
  
  /** Username of the resource owner */
  username?: string;
  
  /** Token type (usually "Bearer") */
  token_type?: string;
  
  /** Token expiration time (seconds since epoch) */
  exp?: number;
  
  /** Token issued at time (seconds since epoch) */
  iat?: number;
  
  /** Token not before time (seconds since epoch) */
  nbf?: number;
  
  /** Subject identifier */
  sub?: string;
  
  /** Intended audience */
  aud?: string | string[];
  
  /** Issuer identifier */
  iss?: string;
  
  /** JWT ID */
  jti?: string;
  
  // Additional server-specific claims may be present
  [key: string]: any;
}

Error Handling

Common error scenarios and handling:

import * as client from "openid-client";

// Introspection error handling
try {
  const result = await client.tokenIntrospection(config, token);
} catch (error) {
  if (error.error === "invalid_request") {
    console.log("Invalid introspection request");
  } else if (error.error === "invalid_token") {
    console.log("Token is invalid or malformed");
  } else if (error.error === "unsupported_token_type") {
    console.log("Server doesn't support introspecting this token type");
  }
}

// Revocation error handling
try {
  await client.tokenRevocation(config, token);
} catch (error) {
  if (error.error === "invalid_request") {
    console.log("Invalid revocation request");
  } else if (error.error === "invalid_token") {
    console.log("Token is invalid or already revoked");
  } else if (error.error === "unsupported_token_type") {
    console.log("Server doesn't support revoking this token type");
  }
  
  // Note: Some servers return success even for invalid tokens
  // Check server documentation for specific behavior
}

Server Capability Detection

Check server support for token management operations:

import * as client from "openid-client";

// Check server metadata for supported operations
const serverMetadata = config.serverMetadata();

// Check for introspection support
if (serverMetadata.introspection_endpoint) {
  console.log("Server supports token introspection");
  
  // Check supported authentication methods for introspection
  if (serverMetadata.introspection_endpoint_auth_methods_supported) {
    console.log("Supported auth methods:", 
      serverMetadata.introspection_endpoint_auth_methods_supported);
  }
}

// Check for revocation support
if (serverMetadata.revocation_endpoint) {
  console.log("Server supports token revocation");
  
  // Check supported authentication methods for revocation
  if (serverMetadata.revocation_endpoint_auth_methods_supported) {
    console.log("Supported auth methods:", 
      serverMetadata.revocation_endpoint_auth_methods_supported);
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-openid-client

docs

advanced-security.md

authorization-flows.md

client-authentication.md

configuration.md

grant-types.md

index.md

passport-integration.md

protected-resources.md

token-management.md

tile.json