Socket
Book a DemoInstallSign in
Socket

@blureffect/oauth2-token-manager

Package Overview
Dependencies
Maintainers
2
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@blureffect/oauth2-token-manager

A scalable OAuth2 token management library with multi-system support

0.2.1
latest
Source
npmnpm
Version published
Maintainers
2
Created
Source

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

# 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.

// 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

Granular 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 provider

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) {
    /* 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
  }
}

Google OAuth2 with Offline Access

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
});

Customizing Authorization Parameters

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 address
  • hd: Google Workspace domain restriction

πŸ”§ 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 minutes

Error 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

πŸ† Credits

Created with ❀️ by Blureffect

Keywords

oauth2

FAQs

Package last updated on 21 Aug 2025

Did you know?

Socket

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.

Install

Related posts

SocketSocket SOC 2 Logo

Product

About

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.

  • Terms
  • Privacy
  • Security

Made with ⚑️ by Socket Inc

U.S. Patent No. 12,346,443 & 12,314,394. Other pending.