Node.js client for OAuth2
—
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.
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.
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 instancereadonly token: TokenObjectImmutable 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"
// }expired(expirationWindowSeconds?: number): booleanDetermines 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(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 requesthttpOptions (object, optional) - HTTP options passed to underlying request libraryReturns: 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(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 revokehttpOptions (object, optional) - HTTP options passed to underlying request libraryReturns: 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
});revokeAll(httpOptions?: any): Promise<void>Revokes both the access token and refresh token.
Parameters:
httpOptions (object, optional) - HTTP options passed to underlying request libraryReturns: 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
});toJSON(): TokenObjectReturns 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);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;
}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');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');
}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();// 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