CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-simple-oauth2

Node.js client for OAuth2

Pending
Overview
Eval results
Files

access-token.mddocs/

Access Token Management

The AccessToken class provides comprehensive token lifecycle management for OAuth 2.0 access tokens, including expiration checking, token refresh, and revocation capabilities. All grant type classes return AccessToken instances that offer these management features.

Overview

AccessToken instances are returned by all grant type methods (getToken() and createToken()) and provide a consistent interface for managing token lifecycles across different OAuth 2.0 flows. The class handles token expiration calculation, refresh operations, and revocation requests.

Core Usage

AccessToken instances are created by grant type classes - you don't typically instantiate them directly:

const { AuthorizationCode } = require('simple-oauth2');

const client = new AuthorizationCode(config);
const accessToken = await client.getToken(params); // Returns AccessToken instance

AccessToken Class

Properties

readonly token: TokenObject

Immutable object containing the token data. This property contains the raw token response from the authorization server, plus any computed properties like expires_at.

Example:

const accessToken = await client.getToken(params);

console.log(accessToken.token);
// {
//   access_token: "eyJhbGciOiJIUzI1...",
//   refresh_token: "def50200ef12...",
//   token_type: "Bearer",
//   expires_in: 3600,
//   expires_at: 2023-12-01T10:30:00.000Z,
//   scope: "read write"
// }

Check Token Expiration

expired(expirationWindowSeconds?: number): boolean

Determines if the access token has expired or is about to expire within a specified window.

Parameters:

  • expirationWindowSeconds (number, optional) - Time window before actual expiration to consider token expired. Defaults to 0.

Returns: Boolean indicating whether the token is expired or about to expire

Example:

const accessToken = await client.getToken(params);

// Check if token is currently expired
if (accessToken.expired()) {
  console.log('Token has expired');
}

// Check if token expires within 5 minutes (300 seconds)
if (accessToken.expired(300)) {
  console.log('Token expires soon, should refresh');
  const refreshedToken = await accessToken.refresh();
}

// Use in a token validation function
function isTokenValid(token, bufferSeconds = 60) {
  return !token.expired(bufferSeconds);
}

Refresh Access Token

refresh(params?: RefreshParams, httpOptions?: any): Promise<AccessToken>

Refreshes the access token using the refresh token. Returns a new AccessToken instance.

Parameters:

  • params.scope (string | string[], optional) - Subset of original scopes to request
  • Additional parameters are automatically serialized for the refresh request
  • httpOptions (object, optional) - HTTP options passed to underlying request library

Returns: Promise resolving to new AccessToken instance

Example:

// Basic token refresh
const refreshedToken = await accessToken.refresh();

// Refresh with reduced scope
const refreshedToken = await accessToken.refresh({
  scope: ['read'] // Request only read scope
});

// Refresh with custom HTTP options
const refreshedToken = await accessToken.refresh({}, {
  timeout: 10000,
  headers: {
    'User-Agent': 'MyApp/1.0'
  }
});

console.log('New access token:', refreshedToken.token.access_token);

Revoke Specific Token

revoke(tokenType: 'access_token' | 'refresh_token', httpOptions?: any): Promise<void>

Revokes either the access token or refresh token.

Parameters:

  • tokenType ('access_token' | 'refresh_token') - Type of token to revoke
  • httpOptions (object, optional) - HTTP options passed to underlying request library

Returns: Promise that resolves when revocation is complete

Example:

// Revoke access token only
await accessToken.revoke('access_token');

// Revoke refresh token only
await accessToken.revoke('refresh_token');

// Revoke with custom HTTP options
await accessToken.revoke('access_token', {
  timeout: 5000
});

Revoke All Tokens

revokeAll(httpOptions?: any): Promise<void>

Revokes both the access token and refresh token.

Parameters:

  • httpOptions (object, optional) - HTTP options passed to underlying request library

Returns: Promise that resolves when both tokens are revoked

Example:

// Revoke both tokens (logout)
await accessToken.revokeAll();

// Revoke with custom HTTP options
await accessToken.revokeAll({
  timeout: 10000
});

Get Token JSON

toJSON(): TokenObject

Returns the token's internal JSON representation for serialization.

Returns: Token object that can be stored or transmitted

Example:

// Store token in database
const tokenData = accessToken.toJSON();
await database.saveToken(userId, tokenData);

// Store token in local storage (browser)
localStorage.setItem('oauth_token', JSON.stringify(accessToken.toJSON()));

// Recreate token from stored data
const storedToken = JSON.parse(localStorage.getItem('oauth_token'));
const recreatedToken = client.createToken(storedToken);

Type Definitions

interface TokenObject {
  access_token: string;
  refresh_token?: string;
  token_type?: string;
  expires_in?: number;
  expires_at?: Date;
  scope?: string;
  [key: string]: any;
}

interface RefreshParams {
  scope?: string | string[];
  [key: string]: any;
}

Common Usage Patterns

Automatic Token Refresh

class TokenManager {
  constructor(grantClient) {
    this.client = grantClient;
    this.currentToken = null;
  }

  async getValidToken() {
    // First time or no cached token
    if (!this.currentToken) {
      throw new Error('No token available. Authenticate first.');
    }

    // Check if token needs refresh (5 minute buffer)
    if (this.currentToken.expired(300)) {
      console.log('Token expired, refreshing...');
      this.currentToken = await this.currentToken.refresh();
    }

    return this.currentToken;
  }

  async setToken(token) {
    this.currentToken = token;
  }

  async makeAuthenticatedRequest(url, options = {}) {
    const token = await this.getValidToken();
    
    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${token.token.access_token}`
      }
    });
  }
}

// Usage
const tokenManager = new TokenManager(oauthClient);

// Initial authentication
const initialToken = await oauthClient.getToken(params);
await tokenManager.setToken(initialToken);

// Make requests (token will auto-refresh if needed)
const response = await tokenManager.makeAuthenticatedRequest('https://api.example.com/data');

Token Persistence with Refresh

class PersistentTokenManager {
  constructor(grantClient, storage) {
    this.client = grantClient;
    this.storage = storage;
  }

  async loadToken() {
    const tokenData = await this.storage.getItem('oauth_token');
    if (!tokenData) return null;

    try {
      const token = this.client.createToken(JSON.parse(tokenData));
      
      // Refresh if expired
      if (token.expired()) {
        const refreshedToken = await token.refresh();
        await this.saveToken(refreshedToken);
        return refreshedToken;
      }

      return token;
    } catch (error) {
      console.error('Failed to load/refresh token:', error);
      await this.storage.removeItem('oauth_token');
      return null;
    }
  }

  async saveToken(token) {
    await this.storage.setItem('oauth_token', JSON.stringify(token.toJSON()));
  }

  async clearToken() {
    await this.storage.removeItem('oauth_token');
  }

  async logout(token) {
    try {
      // Revoke tokens on server
      await token.revokeAll();
    } catch (error) {
      console.warn('Failed to revoke tokens:', error);
    } finally {
      // Clear local storage regardless
      await this.clearToken();
    }
  }
}

// Usage with localStorage
const tokenManager = new PersistentTokenManager(oauthClient, {
  getItem: (key) => Promise.resolve(localStorage.getItem(key)),
  setItem: (key, value) => Promise.resolve(localStorage.setItem(key, value)),
  removeItem: (key) => Promise.resolve(localStorage.removeItem(key))
});

// Load existing token on app start
const existingToken = await tokenManager.loadToken();
if (existingToken) {
  console.log('Loaded existing token');
} else {
  console.log('No valid token found, need to authenticate');
}

Token Expiration Monitoring

class TokenExpirationMonitor {
  constructor(token, onExpiry, checkInterval = 60000) {
    this.token = token;
    this.onExpiry = onExpiry;
    this.checkInterval = checkInterval;
    this.intervalId = null;
  }

  start() {
    this.intervalId = setInterval(() => {
      // Check if token expires within 5 minutes
      if (this.token.expired(300)) {
        this.stop();
        this.onExpiry(this.token);
      }
    }, this.checkInterval);
  }

  stop() {
    if (this.intervalId) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  }

  updateToken(newToken) {
    this.token = newToken;
  }
}

// Usage
const monitor = new TokenExpirationMonitor(
  accessToken,
  async (expiredToken) => {
    console.log('Token is expiring, attempting refresh...');
    try {
      const refreshedToken = await expiredToken.refresh();
      monitor.updateToken(refreshedToken);
      monitor.start(); // Restart monitoring
      console.log('Token refreshed successfully');
    } catch (error) {
      console.error('Failed to refresh token:', error);
      // Handle re-authentication
    }
  }
);

monitor.start();

Secure Token Storage

// Example using Node.js with encrypted storage
const crypto = require('crypto');

class SecureTokenStorage {
  constructor(encryptionKey) {
    this.algorithm = 'aes-256-gcm';
    this.key = crypto.scryptSync(encryptionKey, 'salt', 32);
  }

  encrypt(text) {
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipher(this.algorithm, this.key);
    cipher.setAAD(Buffer.from('oauth-token'));
    
    let encrypted = cipher.update(text, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    
    const authTag = cipher.getAuthTag();
    
    return {
      encrypted,
      iv: iv.toString('hex'),
      authTag: authTag.toString('hex')
    };
  }

  decrypt(encryptedData) {
    const decipher = crypto.createDecipher(this.algorithm, this.key);
    decipher.setAAD(Buffer.from('oauth-token'));
    decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
    
    let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    
    return decrypted;
  }

  async saveToken(token) {
    const tokenJson = JSON.stringify(token.toJSON());
    const encrypted = this.encrypt(tokenJson);
    await fs.writeFile('token.enc', JSON.stringify(encrypted));
  }

  async loadToken(grantClient) {
    try {
      const encryptedData = JSON.parse(await fs.readFile('token.enc', 'utf8'));
      const tokenJson = this.decrypt(encryptedData);
      const tokenData = JSON.parse(tokenJson);
      return grantClient.createToken(tokenData);
    } catch (error) {
      return null;
    }
  }
}

// Usage
const secureStorage = new SecureTokenStorage(process.env.ENCRYPTION_KEY);

// Save token securely
await secureStorage.saveToken(accessToken);

// Load token securely
const loadedToken = await secureStorage.loadToken(oauthClient);

Install with Tessl CLI

npx tessl i tessl/npm-simple-oauth2

docs

access-token.md

authorization-code.md

client-credentials.md

index.md

resource-owner-password.md

tile.json