@blureffect/oauth2-handler v0.2.2
OAuth2 Handler
๐ The simplest, most complete OAuth2 library for Node.js applications
A production-ready OAuth2 handler that just works. Supports multiple providers, persistent storage, automatic token refresh, and advanced account management - all with a developer-friendly API.
โจ Why Choose OAuth2 Handler?
- ๐ง 5-minute setup - Get OAuth working in minutes, not hours
- ๐ Multiple providers - Google, Microsoft, GitHub, and custom providers
- ๐ฅ Multi-account support - Users can connect multiple accounts per provider
- ๐พ Persistent storage - PostgreSQL, Redis, or in-memory options
- ๐ Auto token refresh - Never worry about expired tokens again
- ๐ก๏ธ PKCE security - Enterprise-grade security built-in
- ๐ฆ TypeScript ready - Full type safety and IntelliSense
- ๐งช Test-friendly - Easy mocking and dependency injection
๐ Quick Start
1. Install
npm install @blur-effect/oauth2-handler
# or
pnpm add @blur-effect/oauth2-handler2. Basic Setup (5 minutes)
import { OAuth2Handler } from '@blur-effect/oauth2-handler';
// Create handler
const auth = new OAuth2Handler({
baseUrl: 'http://localhost:3000',
privateKey: process.env.SESSION_SECRET, // Any random string
});
// Register Google OAuth
auth.register('google').withConfig({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
scopes: ['openid', 'email', 'profile'],
});3. Add Routes (Express.js example)
// Route 1: Start OAuth flow
app.get('/auth/google', async (req, res) => {
const authUrl = await auth.authUrl('google', req.sessionID);
res.redirect(authUrl);
});
// Route 2: Handle callback
app.get('/oauth/callback/google', async (req, res) => {
const { code, state } = req.query;
const { agentId, tokens } = await auth.handleCallback(code, state);
// User is now authenticated!
req.session.userId = agentId;
res.redirect('/dashboard');
});
// Route 3: Make API calls
app.get('/profile', async (req, res) => {
const profile = await auth.withValidToken(req.session.userId, 'google', async (token) => {
const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
headers: { Authorization: `Bearer ${token}` }
});
return response.json();
});
res.json(profile);
});4. Set Environment Variables
SESSION_SECRET=your-secret-key-here
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secretThat's it! ๐ Your OAuth is working. Users can now sign in with Google.
๐ Common Use Cases
Single Provider, Single Account
Perfect for simple apps where users sign in with one Google/Microsoft account:
const auth = new OAuth2Handler({
baseUrl: 'https://myapp.com',
privateKey: process.env.SESSION_SECRET,
});
auth.register('google').withConfig({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
scopes: ['openid', 'email', 'profile'],
});
// Usage: Direct API calls
const calendarEvents = await auth.withValidToken(userId, 'google', async (token) => {
const response = await fetch('https://www.googleapis.com/calendar/v3/calendars/primary/events', {
headers: { Authorization: `Bearer ${token}` }
});
return response.json();
});Multiple Providers
Allow users to connect with Google AND Microsoft:
const auth = new OAuth2Handler({
baseUrl: 'https://myapp.com',
privateKey: process.env.SESSION_SECRET,
});
// Google OAuth
auth.register('google').withConfig({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
scopes: ['openid', 'email', 'profile', 'https://www.googleapis.com/auth/calendar.readonly'],
});
// Microsoft OAuth
auth.register('microsoft').withConfig({
clientId: process.env.MICROSOFT_CLIENT_ID,
clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
scopes: ['openid', 'profile', 'email', 'User.Read', 'Calendars.Read'],
pkce: true, // Microsoft recommends PKCE
});
// Routes for both providers
app.get('/auth/google', async (req, res) => {
const authUrl = await auth.authUrl('google', req.sessionID);
res.redirect(authUrl);
});
app.get('/auth/microsoft', async (req, res) => {
const authUrl = await auth.authUrl('microsoft', req.sessionID);
res.redirect(authUrl);
});Multiple Accounts Per Provider
Let users connect multiple Google accounts (work + personal):
import { UniversalTokenStore, PostgresStorageAdapter } from '@blur-effect/oauth2-handler';
// Use persistent storage for multiple accounts
const store = new UniversalTokenStore(
new PostgresStorageAdapter({
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'password',
database: 'myapp',
synchronize: true, // Creates tables automatically
}),
'oauth2:'
);
const auth = new OAuth2Handler({
baseUrl: 'https://myapp.com',
privateKey: process.env.SESSION_SECRET,
store, // Use persistent storage
});
// Register Google with user info fetching
store.registerProvider('google', new (providers.google)());
// Now users can connect multiple Google accounts
// Each account is stored separately and accessible by email
const workAccount = await store.getTokensForEmail(userId, 'google', 'work@company.com');
const personalAccount = await store.getTokensForEmail(userId, 'google', 'personal@gmail.com');๐ข Production Setup
With PostgreSQL (Recommended)
Perfect for production apps that need persistent, reliable storage:
import { OAuth2Handler, UniversalTokenStore, PostgresStorageAdapter } from '@blur-effect/oauth2-handler';
const postgresAdapter = new PostgresStorageAdapter({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
ssl: process.env.NODE_ENV === 'production',
synchronize: process.env.NODE_ENV !== 'production', // Auto-create tables in dev only
});
const store = new UniversalTokenStore(postgresAdapter, 'oauth2:');
const auth = new OAuth2Handler({
baseUrl: process.env.APP_URL,
privateKey: process.env.SESSION_SECRET,
store,
});
// Production environment variables
// DB_HOST=your-postgres-host
// DB_USERNAME=your-db-user
// DB_PASSWORD=your-db-password
// DB_NAME=your-database
// APP_URL=https://yourapp.comWith Redis
Great for high-performance apps with existing Redis infrastructure:
import { UniversalTokenStore, RedisStorageAdapter } from '@blur-effect/oauth2-handler';
import { Redis } from 'ioredis';
// Create your Redis client with your preferred library
const redisClient = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
});
// Pass the Redis client to the adapter
const store = new UniversalTokenStore(
new RedisStorageAdapter(redisClient),
'oauth2:'
);
const auth = new OAuth2Handler({
baseUrl: process.env.APP_URL,
privateKey: process.env.SESSION_SECRET,
store,
});
// โ
Pros: Very fast, scalable, great for high-traffic apps
// โ
Great for: Microservices, high-performance applications๐ Security Best Practices
PKCE (Proof Key for Code Exchange)
Enable PKCE for enhanced security (especially for public clients):
auth.register('microsoft').withConfig({
// ... other config
pkce: true, // Enable PKCE
});Secure Token Storage
Always use persistent storage in production:
// โ DON'T: In-memory storage (default) - tokens lost on restart
const auth = new OAuth2Handler({ /* basic config */ });
// โ
DO: Persistent storage
const auth = new OAuth2Handler({
baseUrl: 'https://myapp.com',
privateKey: process.env.SESSION_SECRET,
store: new UniversalTokenStore(new PostgresStorageAdapter({ /* config */ })),
});Environment Variables
Never hardcode secrets:
# OAuth Provider Credentials
GOOGLE_CLIENT_ID=123456789.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-google-secret
MICROSOFT_CLIENT_ID=your-microsoft-app-id
MICROSOFT_CLIENT_SECRET=your-microsoft-secret
# Database (if using PostgreSQL)
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=your-secure-password
DB_NAME=myapp
# Application
SESSION_SECRET=a-very-long-random-string-at-least-32-characters
APP_URL=https://yourapp.com
NODE_ENV=production๐ค Migration Guides
From Passport.js
Replace Passport strategies with OAuth2Handler:
// Before (Passport.js)
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/auth/google/callback"
}, (accessToken, refreshToken, profile, done) => {
// Manual token and user management
}));
// After (OAuth2Handler)
const auth = new OAuth2Handler({
baseUrl: 'https://myapp.com',
privateKey: process.env.SESSION_SECRET,
});
auth.register('google').withConfig({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
scopes: ['openid', 'email', 'profile'],
});
// Automatic token management, refresh, and persistence!From Custom OAuth Implementation
Replace complex OAuth flows:
// Before: Manual OAuth implementation (100+ lines of code)
// - Manual PKCE implementation
// - Manual state management
// - Manual token refresh logic
// - Manual storage management
// After: OAuth2Handler (5 lines of code)
const auth = new OAuth2Handler({
baseUrl: 'https://myapp.com',
privateKey: process.env.SESSION_SECRET,
});
auth.register('provider').withConfig({ /* provider config */ });
// Done! Everything else is handled automatically.๐ Complete API Reference
OAuth2Handler Methods
const auth = new OAuth2Handler(config);
// Generate authorization URL
const authUrl = await auth.authUrl(provider, agentId, options?);
// Handle OAuth callback
const result = await auth.handleCallback(code, state);
// Make API calls with valid tokens
const data = await auth.withValidToken(agentId, provider, callback, options?);
// Manual token management
const tokens = await auth.getTokens(agentId, provider);
const validTokens = await auth.ensureValidToken(agentId, provider);
const newTokens = await auth.refresh(agentId, provider);
const isExpired = auth.isTokenExpired(tokens, options?);UniversalTokenStore Methods
const store = new UniversalTokenStore(adapter, prefix?);
// Multi-account management
const accounts = await store.getUserAccounts(agentId, provider);
const allAccounts = await store.getAllUserAccounts(agentId);
const tokens = await store.getTokensForEmail(agentId, provider, email);
const deleted = await store.deleteByEmail(agentId, provider, email);
// Advanced querying
const queryBuilder = store.createQueryBuilder();
const filteredAccounts = await store.executeQuery(agentId, queryBuilder);
// Provider registration
store.registerProvider(provider, userInfoFetcher);๐ค Migration Guides
From Passport.js
Replace Passport strategies with OAuth2Handler:
// Before (Passport.js)
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/auth/google/callback"
}, (accessToken, refreshToken, profile, done) => {
// Manual token and user management
}));
// After (OAuth2Handler)
const auth = new OAuth2Handler({
baseUrl: 'https://myapp.com',
privateKey: process.env.SESSION_SECRET,
});
auth.register('google').withConfig({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
scopes: ['openid', 'email', 'profile'],
});
// Automatic token management, refresh, and persistence!From Custom OAuth Implementation
Replace complex OAuth flows:
// Before: Manual OAuth implementation (100+ lines of code)
// - Manual PKCE implementation
// - Manual state management
// - Manual token refresh logic
// - Manual storage management
// After: OAuth2Handler (5 lines of code)
const auth = new OAuth2Handler({
baseUrl: 'https://myapp.com',
privateKey: process.env.SESSION_SECRET,
});
auth.register('provider').withConfig({ /* provider config */ });
// Done! Everything else is handled automatically.๐ Complete API Reference
OAuth2Handler Methods
const auth = new OAuth2Handler(config);
// Generate authorization URL
const authUrl = await auth.authUrl(provider, agentId, options?);
// Handle OAuth callback
const result = await auth.handleCallback(code, state);
// Make API calls with valid tokens
const data = await auth.withValidToken(agentId, provider, callback, options?);
// Manual token management
const tokens = await auth.getTokens(agentId, provider);
const validTokens = await auth.ensureValidToken(agentId, provider);
const newTokens = await auth.refresh(agentId, provider);
const isExpired = auth.isTokenExpired(tokens, options?);UniversalTokenStore Methods
const store = new UniversalTokenStore(adapter, prefix?);
// Multi-account management
const accounts = await store.getUserAccounts(agentId, provider);
const allAccounts = await store.getAllUserAccounts(agentId);
const tokens = await store.getTokensForEmail(agentId, provider, email);
const deleted = await store.deleteByEmail(agentId, provider, email);
// Advanced querying
const queryBuilder = store.createQueryBuilder();
const filteredAccounts = await store.executeQuery(agentId, queryBuilder);
// Provider registration
store.registerProvider(provider, userInfoFetcher);๐ค Storage Adapters
Choose the right storage adapter for your needs:
๐ In-Memory (Default) - Development & Testing
Perfect for getting started, development, and testing:
import { OAuth2Handler } from '@blur-effect/oauth2-handler';
// Uses in-memory storage by default
const auth = new OAuth2Handler({
baseUrl: 'http://localhost:3000',
privateKey: process.env.SESSION_SECRET,
});
// โ
Pros: Zero setup, fast, perfect for development
// โ Cons: Data lost on restart, not suitable for production๐พ PostgreSQL - Production Recommended
Best for production applications requiring reliability:
import { UniversalTokenStore, PostgresStorageAdapter } from '@blur-effect/oauth2-handler';
const postgresAdapter = new PostgresStorageAdapter({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
ssl: process.env.NODE_ENV === 'production',
synchronize: process.env.NODE_ENV !== 'production', // Auto-create tables in dev
});
const store = new UniversalTokenStore(postgresAdapter, 'oauth2:');
const auth = new OAuth2Handler({
baseUrl: process.env.APP_URL,
privateKey: process.env.SESSION_SECRET,
store,
});
// โ
Pros: ACID transactions, reliable, persistent, multi-account support
// โ
Great for: Production apps, multi-user applicationsDatabase Schema (auto-created):
CREATE TABLE oauth_tokens (
key VARCHAR(255) PRIMARY KEY,
value TEXT NOT NULL,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE oauth_user_data (
key VARCHAR(255),
field VARCHAR(255),
value TEXT NOT NULL,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (key, field)
);โก Redis - High Performance
Perfect for high-traffic applications with existing Redis:
import { UniversalTokenStore, RedisStorageAdapter } from '@blur-effect/oauth2-handler';
import { Redis } from 'ioredis';
// Create your Redis client with your preferred library
const redisClient = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
});
// Pass the Redis client to the adapter
const store = new UniversalTokenStore(
new RedisStorageAdapter(redisClient),
'oauth2:'
);
const auth = new OAuth2Handler({
baseUrl: process.env.APP_URL,
privateKey: process.env.SESSION_SECRET,
store,
});
// โ
Pros: Very fast, scalable, great for high-traffic apps
// โ
Great for: Microservices, high-performance applications๐ Advanced Features
๐ Automatic Token Refresh
Never worry about expired tokens:
// The library automatically refreshes tokens when needed
const data = await auth.withValidToken(userId, 'google', async (token) => {
// Token is guaranteed to be valid and non-expired
const response = await fetch('https://api.google.com/some/endpoint', {
headers: { Authorization: `Bearer ${token}` }
});
return response.json();
});
// Manual token management (if needed)
const tokens = await auth.ensureValidToken(userId, 'google'); // Refreshes if expired
const isExpired = auth.isTokenExpired(tokens); // Check expiration๐ฅ Multi-Account Management
Support multiple accounts per provider:
import { UniversalTokenStore, PostgresStorageAdapter, providers } from '@blur-effect/oauth2-handler';
const store = new UniversalTokenStore(postgresAdapter, 'oauth2:');
// Register provider with user info fetching
store.registerProvider('google', new providers.google());
// Users can connect multiple Google accounts
// Each is stored separately and accessible by email
const accounts = await store.getUserAccounts(userId, 'google');
// Returns: [{ email: 'work@company.com', ... }, { email: 'personal@gmail.com', ... }]
// Access specific account
const workTokens = await store.getTokensForEmail(userId, 'google', 'work@company.com');
const personalTokens = await store.getTokensForEmail(userId, 'google', 'personal@gmail.com');๐ Advanced Account Querying
Query and filter accounts with ease:
// Get all accounts across all providers
const allAccounts = await store.getAllUserAccounts(userId);
// Advanced querying with filters
const queryBuilder = store.createQueryBuilder()
.whereProviderIn(['google', 'microsoft'])
.whereEmailContains('@company.com')
.whereLastUsedAfter(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)) // Last 30 days
.orderBy('lastUsed', 'desc')
.limit(10);
const filteredAccounts = await store.executeQuery(userId, queryBuilder);๐ ๏ธ Framework Examples
Express.js (Complete Example)
import express from 'express';
import session from 'express-session';
import { OAuth2Handler, UniversalTokenStore, PostgresStorageAdapter } from '@blur-effect/oauth2-handler';
const app = express();
// Session middleware
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
}));
// Setup OAuth2Handler
const postgresAdapter = new PostgresStorageAdapter({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
synchronize: true, // Auto-create tables
});
const store = new UniversalTokenStore(postgresAdapter, 'oauth2:');
const auth = new OAuth2Handler({
baseUrl: process.env.APP_URL || 'http://localhost:3000',
privateKey: process.env.SESSION_SECRET,
store,
});
// Register providers
auth.register('google').withConfig({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
scopes: ['openid', 'email', 'profile', 'https://www.googleapis.com/auth/calendar.readonly'],
});
// Routes
app.get('/auth/:provider', async (req, res) => {
const { provider } = req.params;
try {
const authUrl = await auth.authUrl(provider, req.sessionID);
res.redirect(authUrl);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.get('/oauth/callback/:provider', async (req, res) => {
const { code, state } = req.query;
try {
const { agentId, tokens } = await auth.handleCallback(code as string, state as string);
req.session.userId = agentId;
res.redirect('/dashboard');
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.get('/api/profile', async (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
try {
const profile = await auth.withValidToken(req.session.userId, 'google', async (token) => {
const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
headers: { Authorization: `Bearer ${token}` }
});
return response.json();
});
res.json(profile);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => console.log('Server running on http://localhost:3000'));Next.js (API Routes)
// pages/api/auth/[...auth].ts
import { OAuth2Handler } from '@blur-effect/oauth2-handler';
import { NextApiRequest, NextApiResponse } from 'next';
const auth = new OAuth2Handler({
baseUrl: process.env.NEXTAUTH_URL,
privateKey: process.env.NEXTAUTH_SECRET,
});
auth.register('google').withConfig({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
scopes: ['openid', 'email', 'profile'],
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { auth: authPath } = req.query;
if (authPath[0] === 'signin' && authPath[1]) {
// Start OAuth flow
const provider = authPath[1];
const authUrl = await auth.authUrl(provider, req.headers['x-forwarded-for'] || req.connection.remoteAddress);
res.redirect(authUrl);
} else if (authPath[0] === 'callback' && authPath[1]) {
// Handle callback
const { code, state } = req.query;
const { agentId, tokens } = await auth.handleCallback(code as string, state as string);
// Set session cookie or JWT token
res.setHeader('Set-Cookie', `session=${agentId}; HttpOnly; Secure; SameSite=Strict`);
res.redirect('/dashboard');
}
}๐จ Troubleshooting
Common Issues
"Invalid redirect URI"
// โ Wrong: Mismatch between config and OAuth provider
auth.register('google').withConfig({
// ... other config
// Make sure this matches EXACTLY in Google Console
});
// โ
Correct: Ensure redirect URI matches
// Google Console: http://localhost:3000/oauth/callback/google
// Your route: app.get('/oauth/callback/google', ...)"Tokens not persisting"
// โ Wrong: Using default in-memory storage
const auth = new OAuth2Handler({ /* config */ });
// โ
Correct: Use persistent storage
const auth = new OAuth2Handler({
baseUrl: 'https://myapp.com',
privateKey: process.env.SESSION_SECRET,
store: new UniversalTokenStore(new PostgresStorageAdapter({ /* config */ })),
});"PKCE required error"
// โ Some providers require PKCE
auth.register('azure').withConfig({
// ... config without PKCE
});
// โ
Enable PKCE for providers that require it
auth.register('azure').withConfig({
// ... other config
pkce: true,
});Debug Mode
Enable detailed logging:
const auth = new OAuth2Handler({
baseUrl: 'https://myapp.com',
privateKey: process.env.SESSION_SECRET,
// Add debug logging
debug: process.env.NODE_ENV === 'development',
});๐งช Testing
Unit Testing
import { OAuth2Handler, InMemoryOAuth2TokenStore } from '@blur-effect/oauth2-handler';
describe('OAuth2 Flow', () => {
let auth: OAuth2Handler;
beforeEach(() => {
auth = new OAuth2Handler({
baseUrl: 'http://localhost:3000',
privateKey: 'test-secret',
store: new InMemoryOAuth2TokenStore(), // Use in-memory for tests
});
auth.register('google').withConfig({
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
scopes: ['openid', 'email'],
});
});
it('should generate auth URL', async () => {
const authUrl = await auth.authUrl('google', 'test-user');
expect(authUrl).toContain('accounts.google.com');
expect(authUrl).toContain('client_id=test-client-id');
});
// Mock token exchange for testing
it('should handle callback', async () => {
// Mock the token exchange
jest.spyOn(global, 'fetch').mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({
access_token: 'mock-access-token',
refresh_token: 'mock-refresh-token',
expires_in: 3600,
}),
} as any);
const result = await auth.handleCallback('mock-code', 'mock-state');
expect(result.tokens.accessToken).toBe('mock-access-token');
});
});๐ฆ Examples & Templates
๐ Ready-to-Run Examples
We provide complete, working examples:
- PostgreSQL Example - Complete multi-provider app with PostgreSQL
- In-Memory Example - Simple setup for quick testing
- Next.js Template (coming soon) - Full Next.js integration
- Fastify Template (coming soon) - High-performance Fastify setup
๐ฏ Live Demo
Try the library with our live demo:
- Multi-Provider Demo - See all features in action
๐ค Contributing
We welcome contributions! Check out our Contributing Guide for details.
๐ License
MIT License. See LICENSE for details.
๐ Quick Links
- PostgreSQL Example โ - Complete working example
- API Documentation โ - Full API reference
- Migration Guide โ - From other libraries
- Troubleshooting โ - Common issues & solutions
Made with โค๏ธ for the Node.js community
โญ Star us on GitHub if this library helped you!
5 months ago
5 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
7 months ago
7 months ago