
Research
/Security News
DuckDB npm Account Compromised in Continuing Supply Chain Attack
Ongoing npm supply chain attack spreads to DuckDB: multiple packages compromised with the same wallet-drainer malware.
@blureffect/oauth2-token-manager
Advanced tools
A scalable OAuth2 token management library with multi-system support
A powerful, storage-agnostic OAuth2 token management library built for scalable multi-system architectures. This library provides comprehensive token lifecycle management with pluggable storage adapters, built-in security features, and support for multiple OAuth2 providers.
npm install @blureffect/oauth2-token-manager
# PostgreSQL adapter
npm install @blureffect/oauth2-storage-postgres
import { OAuth2Client } from '@blureffect/oauth2-token-manager';
// Quick setup for common use cases
const oauth = await OAuth2Client.quickSetup('MyApp', {
google: {
clientId: 'your-google-client-id',
clientSecret: 'your-google-client-secret',
authorizationUrl: 'https://accounts.google.com/o/oauth2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
redirectUri: 'http://localhost:3000/auth/callback',
scopes: ['profile', 'email'],
},
github: {
clientId: 'your-github-client-id',
clientSecret: 'your-github-client-secret',
authorizationUrl: 'https://github.com/login/oauth/authorize',
tokenUrl: 'https://github.com/login/oauth/access_token',
redirectUri: 'http://localhost:3000/auth/callback',
scopes: ['user:email'],
},
});
// Create or get a user
const user = await oauth.getOrCreateUser({
email: 'user@example.com',
metadata: { role: 'user' },
});
// Start OAuth flow
const { url, state } = await oauth.authorize({
provider: 'google',
scopes: ['profile', 'email'],
});
// Handle callback
const result = await oauth.handleCallback(code, state);
console.log('User authenticated:', result.userId);
import { OAuth2Client } from '@blureffect/oauth2-token-manager';
import { PostgresStorageFactory } from '@blureffect/oauth2-storage-postgres';
// Custom storage adapter
const storage = await PostgresStorageFactory.create({
host: 'localhost',
port: 5432,
username: 'oauth2_user',
password: 'secure_password',
database: 'oauth2_db',
});
const oauth = new OAuth2Client({
storage,
providers: {
google: {
/* config */
},
github: {
/* config */
},
},
});
// Create system and scopes
const system = await oauth.createSystem('MyApp');
const scope = await oauth.createScope('api-access', {
type: 'access',
permissions: ['read:profile', 'write:data'],
isolated: true,
});
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β OAuth2Client β
β βββββββββββββββββββ βββββββββββββββββββββββββββββββββββ β
β β Context API β β Granular API β β
β β (Simplified) β β (Full Control) β β
β βββββββββββββββββββ βββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββΌββββββββββββββββ
β β β
βββββββββββΌβββββββββ βββββΌβββββββββ βββββΌββββββββββ
β Providers β β Storage β β Profile β
β (OAuth2) β β Adapter β β Fetchers β
ββββββββββββββββββββ ββββββββββββββ βββββββββββββββ
Important: Users can have multiple tokens for the same provider within the same scope. This allows for scenarios like different email accounts or token refresh cycles.
// Systems: Top-level applications/services
interface System {
id: string;
name: string;
description?: string;
scopes: Scope[];
metadata?: Record<string, any>;
}
// Scopes: Permission boundaries within systems
interface Scope {
id: string;
systemId: string;
name: string;
type: 'authentication' | 'access' | 'custom';
permissions: string[];
isolated: boolean; // Whether tokens are isolated to this scope
}
// Users: Identity within a system
interface User {
id: string;
systemId: string;
metadata?: Record<string, any>;
}
// User Tokens: OAuth2 tokens tied to user/system/scope/provider
// A user can have MULTIPLE tokens for the same provider/scope combination
interface UserToken {
id: string;
userId: string;
systemId: string;
scopeId: string;
provider: string;
token: OAuth2Token;
}
The main client class providing both context-managed and granular APIs.
// System management
await oauth.createSystem('MyApp');
await oauth.useSystem(systemId);
// User management
const user = await oauth.getOrCreateUser({ email: 'user@example.com' });
await oauth.useUser(userId);
// Authorization flow
const { url, state } = await oauth.authorize({ provider: 'google' });
const result = await oauth.handleCallback(code, state);
// Token operations (uses current context + default scope)
// Note: When multiple tokens exist, these methods use the first (most recent) token
const accessToken = await oauth.getAccessToken('google');
const validToken = await oauth.ensureValidToken('google');
// Get all user tokens with auto-refresh (for current user)
const userTokens = await oauth.getUserTokens();
// Get all valid tokens for a specific user
const allTokens = await oauth.getAllValidTokensForUser(userId);
// Returns: { provider: string; scopeId: string; token: OAuth2Token; userToken: UserToken }[]
// Revoke tokens (uses current context)
await oauth.revokeTokens('google'); // Revokes for current user/scope/provider
The granular API provides full control over the token hierarchy:
// === User-Centric Token Queries (Primary Key: User) ===
// Get ALL tokens for a user across all scopes/providers
const userTokens = await oauth.granular.getTokensByUser(userId);
// Get tokens for user in specific scope (across all providers)
const scopeTokens = await oauth.granular.getTokensByUserAndScope(userId, scopeId);
// Get tokens for user with specific provider (across all scopes)
const providerTokens = await oauth.granular.getTokensByUserAndProvider(userId, 'google');
// Get tokens for user/scope/provider combination (can be multiple!)
const specificTokens = await oauth.granular.getTokensByUserScopeProvider(userId, scopeId, 'google');
// === Cross-User Queries (System/Scope Level) ===
// Get all tokens in a scope across all users
const scopeAllTokens = await oauth.granular.getTokensByScope(systemId, scopeId);
// Get all tokens for a provider across all users in system
const providerAllTokens = await oauth.granular.getTokensByProvider(systemId, 'google');
// Get all tokens in a system
const systemTokens = await oauth.granular.getTokensBySystem(systemId);
// === Email-Based Queries ===
// Find tokens by email (cross-user, cross-provider)
const emailTokens = await oauth.granular.findTokensByEmail('user@example.com', systemId);
// Find tokens by email in specific scope
const emailScopeTokens = await oauth.granular.findTokensByEmailAndScope(
'user@example.com',
systemId,
scopeId,
);
// Find tokens by email for specific provider
const emailProviderTokens = await oauth.granular.findTokensByEmailAndProvider(
'user@example.com',
systemId,
'google',
);
// Find specific token by email/scope/provider (returns single token or null)
const specificToken = await oauth.granular.findTokenByEmailScopeProvider(
'user@example.com',
systemId,
scopeId,
'google',
);
// === Token Operations ===
// Get valid token for user (auto-refresh, takes first if multiple exist)
const validToken = await oauth.granular.getValidTokenForUser(userId, scopeId, 'google');
// Get access token for user (convenience method)
const accessToken = await oauth.granular.getAccessTokenForUser(userId, scopeId, 'google');
// Save new token for user
const savedToken = await oauth.granular.saveTokenForUser(
userId,
systemId,
scopeId,
'google',
'user@example.com',
oauthToken,
);
// === Token Management ===
// Delete tokens by different criteria
await oauth.granular.deleteTokensByUser(userId); // All tokens for user
await oauth.granular.deleteTokensByUserAndScope(userId, scopeId); // User's tokens in scope
await oauth.granular.deleteTokensByUserAndProvider(userId, 'google'); // User's tokens for provider
import { InMemoryStorageAdapter } from '@blureffect/oauth2-token-manager';
const storage = new InMemoryStorageAdapter();
const oauth = new OAuth2Client({ storage });
import { PostgresStorageFactory } from '@blureffect/oauth2-storage-postgres';
const storage = await PostgresStorageFactory.create({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
ssl: process.env.NODE_ENV === 'production',
});
import { StorageAdapter } from '@blureffect/oauth2-token-manager';
class MyCustomAdapter implements StorageAdapter {
async createSystem(system) {
/* implement */
}
async getSystem(id) {
/* implement */
}
// ... implement all required methods
}
{
google: {
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
authorizationUrl: 'https://accounts.google.com/o/oauth2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
redirectUri: 'http://localhost:3000/auth/callback',
scopes: ['profile', 'email'],
profileUrl: 'https://www.googleapis.com/oauth2/v2/userinfo',
usePKCE: true, // Recommended for security
}
}
{
github: {
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
authorizationUrl: 'https://github.com/login/oauth/authorize',
tokenUrl: 'https://github.com/login/oauth/access_token',
redirectUri: 'http://localhost:3000/auth/callback',
scopes: ['user:email'],
profileUrl: 'https://api.github.com/user',
}
}
{
custom: {
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
authorizationUrl: 'https://provider.com/oauth/authorize',
tokenUrl: 'https://provider.com/oauth/token',
redirectUri: 'http://localhost:3000/auth/callback',
scopes: ['read', 'write'],
profileUrl: 'https://provider.com/api/user',
additionalParams: {
audience: 'api.example.com'
},
responseRootKey: 'data' // For nested responses
}
}
const oauth = new OAuth2Client({
providers: {
google: {
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
redirectUri: 'http://localhost:3000/auth/callback',
scopes: ['profile', 'email'],
// Override default offline access parameters
extraAuthParams: {
access_type: 'offline', // Request refresh token
prompt: 'consent', // Force consent screen
include_granted_scopes: 'true', // Include previously granted scopes
},
},
},
});
// The library automatically handles refresh tokens
const token = await oauth.getAccessToken('google', {
autoRefresh: true,
refreshBuffer: 5, // Refresh 5 minutes before expiry
});
Each provider supports customization through extraAuthParams
and additionalParams
:
{
google: {
// ... other config ...
extraAuthParams: {
access_type: 'offline', // For refresh tokens
prompt: 'select_account', // Force account selection
hd: 'yourdomain.com' // Limit to specific Google Workspace domain
}
},
microsoft: {
// ... other config ...
extraAuthParams: {
prompt: 'select_account',
domain_hint: 'yourdomain.com'
}
}
}
Available parameters for Google OAuth2:
access_type
: 'online' (default) or 'offline' (for refresh tokens)prompt
: 'none', 'consent', 'select_account'include_granted_scopes
: 'true' or 'false'login_hint
: User's email addresshd
: Google Workspace domain restrictionconst accessToken = await oauth.getAccessToken('google', {
autoRefresh: true,
refreshBuffer: 5, // Refresh 5 minutes before expiry
expirationBuffer: 30, // Consider expired 30 seconds early
});
const result = await oauth.handleCallback(code, state, {
profileOptions: {
checkProfileEmail: true, // Fetch and check email conflicts
replaceConflictingTokens: true, // Replace existing tokens with same email
mergeUserData: true, // Merge profile data into user metadata
},
});
// Get all valid tokens for an email across all providers in a system
// Note: This returns an array since one email can have tokens from multiple providers
const emailTokens = await oauth.getAllValidTokensForEmail('user@example.com', systemId);
// Returns: { provider: string; scopeId: string; token: OAuth2Token; userToken: UserToken }[]
// Get specific token by email (returns single token or null)
// This enforces the email uniqueness rule within provider/scope
const token = await oauth.getTokenForEmail('user@example.com', systemId, scopeId, 'google');
// Get valid token for email with auto-refresh
const validToken = await oauth.getValidTokenForEmail(
'user@example.com',
systemId,
scopeId,
'google',
{ autoRefresh: true },
);
// Get access token for email
const accessToken = await oauth.getAccessTokenForEmail(
'user@example.com',
systemId,
scopeId,
'google',
);
// Execute with valid token for email
await oauth.withValidTokenForEmail(
'user@example.com',
systemId,
scopeId,
'google',
async (accessToken) => {
console.log('Using token for email:', accessToken);
},
);
// Check if email has token for specific provider/scope
const hasToken = await oauth.hasTokenForEmail('user@example.com', systemId, scopeId, 'google');
// Revoke tokens for email
await oauth.revokeTokensForEmail('user@example.com', systemId, scopeId, 'google');
For backend APIs where you have explicit user IDs:
// Get access token for specific user/scope/provider
// Note: Takes the first (most recent) token if multiple exist
const accessToken = await oauth.getAccessTokenForUser(userId, systemId, scopeId, 'google', {
autoRefresh: true,
});
// Execute with valid token for specific user
await oauth.withValidTokenForUser(userId, systemId, scopeId, 'google', async (accessToken) => {
// Make API calls with the token
return apiResponse;
});
// Get all valid tokens for a user with auto-refresh
const userTokens = await oauth.getAllValidTokensForUser(userId, {
autoRefresh: true,
refreshBuffer: 5, // Refresh 5 minutes before expiry
});
// Check if user has tokens for specific provider/scope
const hasToken = await oauth.hasTokenForUser(userId, systemId, scopeId, 'google');
// Get user token entity (includes metadata)
const userToken = await oauth.getUserTokenForUser(userId, systemId, scopeId, 'google');
// Revoke specific tokens
await oauth.revokeTokensForUser(userId, systemId, scopeId, 'google');
// Enable PKCE for enhanced security
const { url, state } = await oauth.authorize({
provider: 'google',
usePKCE: true, // Enables PKCE flow
});
// Check if token is expired
const isExpired = oauth.isTokenExpired(token, {
expirationBuffer: 60, // Consider expired 60 seconds early
});
// Ensure valid token (auto-refresh if needed)
const validToken = await oauth.ensureValidToken('google');
The library includes comprehensive tests using Vitest:
# Run tests
npm test
# Run tests with UI
npm run test:ui
# Run tests with coverage
npm run test:coverage
# Watch mode
npm run test:watch
// Create systems for different applications
const crmSystem = await oauth.createSystem('CRM App');
const analyticsSystem = await oauth.createSystem('Analytics Dashboard');
// Create scopes for different access levels
await oauth.useSystem(crmSystem.id);
const readScope = await oauth.createScope('read-only', {
type: 'access',
permissions: ['read:contacts', 'read:deals'],
isolated: true,
});
const adminScope = await oauth.createScope('admin', {
type: 'access',
permissions: ['*'],
isolated: true,
});
// Users can have different permissions per system
const user = await oauth.getOrCreateUser({ email: 'user@company.com' });
// Authorize for specific system/scope
const { url } = await oauth.authorize({
provider: 'google',
scopes: ['profile', 'email'],
});
// Each tenant gets their own system
const tenantSystem = await oauth.createSystem(`Tenant-${tenantId}`);
// Tenant-specific user management
await oauth.useSystem(tenantSystem.id);
const tenantUser = await oauth.getOrCreateUser({
email: userEmail,
metadata: { tenantId, role: 'admin' },
});
// Tenant-isolated tokens
const tokens = await oauth.granular.getTokensBySystem(tenantSystem.id);
const oauth = new OAuth2Client({
storage: await PostgresStorageFactory.create({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
ssl: {
rejectUnauthorized: process.env.NODE_ENV === 'production',
},
poolSize: 20,
logging: process.env.NODE_ENV === 'development',
}),
sealKey: process.env.OAUTH2_SEAL_KEY, // For token encryption
providers: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
redirectUri: process.env.GOOGLE_REDIRECT_URI,
// ... other config
},
},
});
// Use token caching for high-traffic scenarios
const accessToken = await oauth.getAccessToken('google', {
autoRefresh: true,
refreshBuffer: 10, // Refresh early to avoid expiry
});
// Batch operations for efficiency
const allTokens = await oauth.getAllValidTokensForUser(userId);
// Clean up expired states regularly
setInterval(
async () => {
await oauth.cleanup(10 * 60 * 1000); // 10 minutes
},
5 * 60 * 1000,
); // Every 5 minutes
try {
const token = await oauth.getAccessToken('google');
} catch (error) {
if (error.message.includes('Token expired')) {
// Handle token expiry
const { url } = await oauth.authorize({ provider: 'google' });
// Redirect to re-authorization
} else if (error.message.includes('Provider not found')) {
// Handle missing provider
}
}
git checkout -b feature/amazing-feature
)git commit -m 'Add some amazing feature'
)git push origin feature/amazing-feature
)# Install dependencies
npm install
# Run in development mode
npm run dev
# Run tests
npm test
# Build the project
npm run build
# Lint and format
npm run lint:fix
npm run format
This project is licensed under the MIT License - see the LICENSE file for details.
Created with β€οΈ by Blureffect
FAQs
A scalable OAuth2 token management library with multi-system support
We found that @blureffect/oauth2-token-manager demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
/Security News
Ongoing npm supply chain attack spreads to DuckDB: multiple packages compromised with the same wallet-drainer malware.
Security News
The MCP Steering Committee has launched the official MCP Registry in preview, a central hub for discovering and publishing MCP servers.
Product
Socketβs new Pull Request Stories give security teams clear visibility into dependency risks and outcomes across scanned pull requests.