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-manager
Storage Adapters
npm install @blureffect/oauth2-storage-postgres
🚀 Quick Start
Simple Setup
import { OAuth2Client } from '@blureffect/oauth2-token-manager';
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'],
},
});
const user = await oauth.getOrCreateUser({
email: 'user@example.com',
metadata: { role: 'user' },
});
const { url, state } = await oauth.authorize({
provider: 'google',
scopes: ['profile', 'email'],
});
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';
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: {
},
github: {
},
},
});
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.
interface System {
id: string;
name: string;
description?: string;
scopes: Scope[];
metadata?: Record<string, any>;
}
interface Scope {
id: string;
systemId: string;
name: string;
type: 'authentication' | 'access' | 'custom';
permissions: string[];
isolated: boolean;
}
interface User {
id: string;
systemId: string;
metadata?: Record<string, any>;
}
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)
await oauth.createSystem('MyApp');
await oauth.useSystem(systemId);
const user = await oauth.getOrCreateUser({ email: 'user@example.com' });
await oauth.useUser(userId);
const { url, state } = await oauth.authorize({ provider: 'google' });
const result = await oauth.handleCallback(code, state);
const accessToken = await oauth.getAccessToken('google');
const validToken = await oauth.ensureValidToken('google');
const userTokens = await oauth.getUserTokens();
const allTokens = await oauth.getAllValidTokensForUser(userId);
await oauth.revokeTokens('google');
Granular API (Advanced)
The granular API provides full control over the token hierarchy:
const userTokens = await oauth.granular.getTokensByUser(userId);
const scopeTokens = await oauth.granular.getTokensByUserAndScope(userId, scopeId);
const providerTokens = await oauth.granular.getTokensByUserAndProvider(userId, 'google');
const specificTokens = await oauth.granular.getTokensByUserScopeProvider(userId, scopeId, 'google');
const scopeAllTokens = await oauth.granular.getTokensByScope(systemId, scopeId);
const providerAllTokens = await oauth.granular.getTokensByProvider(systemId, 'google');
const systemTokens = await oauth.granular.getTokensBySystem(systemId);
const emailTokens = await oauth.granular.findTokensByEmail('user@example.com', systemId);
const emailScopeTokens = await oauth.granular.findTokensByEmailAndScope(
'user@example.com',
systemId,
scopeId,
);
const emailProviderTokens = await oauth.granular.findTokensByEmailAndProvider(
'user@example.com',
systemId,
'google',
);
const specificToken = await oauth.granular.findTokenByEmailScopeProvider(
'user@example.com',
systemId,
scopeId,
'google',
);
const validToken = await oauth.granular.getValidTokenForUser(userId, scopeId, 'google');
const accessToken = await oauth.granular.getAccessTokenForUser(userId, scopeId, 'google');
const savedToken = await oauth.granular.saveTokenForUser(
userId,
systemId,
scopeId,
'google',
'user@example.com',
oauthToken,
);
await oauth.granular.deleteTokensByUser(userId);
await oauth.granular.deleteTokensByUserAndScope(userId, scopeId);
await oauth.granular.deleteTokensByUserAndProvider(userId, 'google');
Storage 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) {
}
async getSystem(id) {
}
}
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,
}
}
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'
}
}
🔧 Advanced Features
Token Auto-Refresh
const accessToken = await oauth.getAccessToken('google', {
autoRefresh: true,
refreshBuffer: 5,
expirationBuffer: 30,
});
Profile-Based Token Management
const result = await oauth.handleCallback(code, state, {
profileOptions: {
checkProfileEmail: true,
replaceConflictingTokens: true,
mergeUserData: true,
},
});
Email-Based Operations
const emailTokens = await oauth.getAllValidTokensForEmail('user@example.com', systemId);
const token = await oauth.getTokenForEmail('user@example.com', systemId, scopeId, 'google');
const validToken = await oauth.getValidTokenForEmail(
'user@example.com',
systemId,
scopeId,
'google',
{ autoRefresh: true },
);
const accessToken = await oauth.getAccessTokenForEmail(
'user@example.com',
systemId,
scopeId,
'google',
);
await oauth.withValidTokenForEmail(
'user@example.com',
systemId,
scopeId,
'google',
async (accessToken) => {
console.log('Using token for email:', accessToken);
},
);
const hasToken = await oauth.hasTokenForEmail('user@example.com', systemId, scopeId, 'google');
await oauth.revokeTokensForEmail('user@example.com', systemId, scopeId, 'google');
User-Centric Operations (Stateless)
For backend APIs where you have explicit user IDs:
const accessToken = await oauth.getAccessTokenForUser(userId, systemId, scopeId, 'google', {
autoRefresh: true,
});
await oauth.withValidTokenForUser(userId, systemId, scopeId, 'google', async (accessToken) => {
return apiResponse;
});
const userTokens = await oauth.getAllValidTokensForUser(userId, {
autoRefresh: true,
refreshBuffer: 5,
});
const hasToken = await oauth.hasTokenForUser(userId, systemId, scopeId, 'google');
const userToken = await oauth.getUserTokenForUser(userId, systemId, scopeId, 'google');
await oauth.revokeTokensForUser(userId, systemId, scopeId, 'google');
PKCE Support
const { url, state } = await oauth.authorize({
provider: 'google',
usePKCE: true,
});
Token Validation
const isExpired = oauth.isTokenExpired(token, {
expirationBuffer: 60,
});
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:
npm test
npm run test:ui
npm run test:coverage
npm run test:watch
🏢 Multi-System Examples
SaaS Platform with Multiple Apps
const crmSystem = await oauth.createSystem('CRM App');
const analyticsSystem = await oauth.createSystem('Analytics Dashboard');
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,
});
const user = await oauth.getOrCreateUser({ email: 'user@company.com' });
const { url } = await oauth.authorize({
provider: 'google',
scopes: ['profile', 'email'],
});
Multi-Tenant Application
const tenantSystem = await oauth.createSystem(`Tenant-${tenantId}`);
await oauth.useSystem(tenantSystem.id);
const tenantUser = await oauth.getOrCreateUser({
email: userEmail,
metadata: { tenantId, role: 'admin' },
});
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,
providers: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
redirectUri: process.env.GOOGLE_REDIRECT_URI,
},
},
});
Performance Optimization
const accessToken = await oauth.getAccessToken('google', {
autoRefresh: true,
refreshBuffer: 10,
});
const allTokens = await oauth.getAllValidTokensForUser(userId);
setInterval(
async () => {
await oauth.cleanup(10 * 60 * 1000);
},
5 * 60 * 1000,
);
Error Handling
try {
const token = await oauth.getAccessToken('google');
} catch (error) {
if (error.message.includes('Token expired')) {
const { url } = await oauth.authorize({ provider: 'google' });
} else if (error.message.includes('Provider not found')) {
}
}
🤝 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
npm install
npm run dev
npm test
npm run build
npm run lint:fix
npm run format
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙋♂️ Support
🏆 Credits
Created with ❤️ by Blureffect