@blureffect/oauth2-token-manager v0.1.0
OAuth2 Token Manager
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.
๐ Features
- ๐ Storage Agnostic: Use any storage backend (In-Memory, PostgreSQL, or build your own adapter)
- ๐ข Multi-System Support: Manage tokens across multiple applications/systems
- ๐ Advanced Security: PKCE support, state validation, token encryption
- โก High Performance: Efficient token validation, caching, and refresh strategies
- ๐ Auto-Refresh: Automatic token refresh with configurable buffers
- ๐ค User Management: Comprehensive user lifecycle with email/external ID support
- ๐ง Profile Integration: Automatic profile fetching from OAuth providers
- ๐ฏ Flexible Scoping: Fine-grained permission management
- ๐ก Developer Friendly: Both context-managed and granular APIs
- ๐งช Fully Tested: Comprehensive test coverage with Vitest
๐ฆ Installation
npm install @blureffect/oauth2-token-managerStorage Adapters
# PostgreSQL adapter
npm install @blureffect/oauth2-storage-postgres๐ Quick Start
Simple Setup
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);Advanced Setup with Custom Storage
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,
});๐๏ธ Architecture
Core Components
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ OAuth2Client โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Context API โ โ Granular API โ โ
โ โ (Simplified) โ โ (Full Control) โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโ
โ โ โ
โโโโโโโโโโโผโโโโโโโโโ โโโโโผโโโโโโโโโ โโโโโผโโโโโโโโโโ
โ Providers โ โ Storage โ โ Profile โ
โ (OAuth2) โ โ Adapter โ โ Fetchers โ
โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโโData Model & Token Hierarchy
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;
}Token Hierarchy Rules
- One User belongs to One System
- One User can have tokens in Multiple Scopes within their system
- One User in One Scope can have tokens from Multiple Providers
- One User in One Scope from One Provider can have Multiple Tokens
- Email Uniqueness: For the same provider, a user cannot have multiple tokens with the same email (validated via profile fetcher)
- Cross-Provider Emails: The same email can exist across different providers
๐ API Reference
OAuth2Client
The main client class providing both context-managed and granular APIs.
Context-Managed API (Recommended)
// 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/providerGranular API (Advanced)
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 providerStorage Adapters
Built-in Memory Adapter
import { InMemoryStorageAdapter } from '@blureffect/oauth2-token-manager';
const storage = new InMemoryStorageAdapter();
const oauth = new OAuth2Client({ storage });PostgreSQL Adapter
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',
});Custom Storage Adapter
import { StorageAdapter } from '@blureffect/oauth2-token-manager';
class MyCustomAdapter implements StorageAdapter {
async createSystem(system) {
/* implement */
}
async getSystem(id) {
/* implement */
}
// ... implement all required methods
}Provider Configuration
Google OAuth2
{
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 OAuth2
{
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',
}
}Generic Provider
{
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
}
}๐ง Advanced Features
Token Auto-Refresh
const accessToken = await oauth.getAccessToken('google', {
autoRefresh: true,
refreshBuffer: 5, // Refresh 5 minutes before expiry
expirationBuffer: 30, // Consider expired 30 seconds early
});Profile-Based Token Management
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
},
});Email-Based Operations
// 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');User-Centric Operations (Stateless)
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');PKCE Support
// Enable PKCE for enhanced security
const { url, state } = await oauth.authorize({
provider: 'google',
usePKCE: true, // Enables PKCE flow
});Token Validation
// 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');๐ Security Features
State Management
- Cryptographically secure state generation
- Automatic state validation and cleanup
- Configurable state expiration
PKCE (Proof Key for Code Exchange)
- Built-in PKCE support for public clients
- Automatic code verifier generation
- Enhanced security for mobile and SPA applications
Token Encryption
- Secure token storage with optional encryption
- Configurable seal keys for sensitive data
- Protection against token theft
Email Validation
- Automatic email conflict detection
- Profile-based user validation
- Cross-provider email consistency
๐งช Testing
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๐ข Multi-System Examples
SaaS Platform with Multiple Apps
// 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'],
});Multi-Tenant Application
// 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);๐ Production Deployment
Environment Configuration
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
},
},
});Performance Optimization
// 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 minutesError Handling
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
}
}๐ค Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
# 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๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐โโ๏ธ Support
- ๐ Documentation
- ๐ Issue Tracker
- ๐ฌ Discussions
๐ Credits
Created with โค๏ธ by Blureffect
6 months ago