Authentication and Authorization - Production Patterns for Secure Applications
Master authentication and authorization with OAuth 2.0, JWT tokens, RBAC, ABAC, session management, passwordless authentication, and security best practices for production applications.
Authentication and authorization form the foundation of application security, controlling who can access your system and what they can do once authenticated. This comprehensive guide covers production-ready patterns for implementing secure authentication and authorization in modern applications.
Authentication vs Authorization
Authentication verifies who you are - confirming a user's identity through credentials like passwords, biometrics, or tokens.
Authorization determines what you can do - defining which resources and actions an authenticated user can access.
These concepts work together but serve distinct purposes. Poor implementation of either creates security vulnerabilities that attackers exploit.
OAuth 2.0 and OpenID Connect
OAuth 2.0 provides industry-standard authorization flows for delegated access, while OpenID Connect (OIDC) adds an identity layer for authentication.
Authorization Code Flow
The most secure OAuth 2.0 flow for web applications:
// Express.js OAuth 2.0 authorization code flow
import express from 'express';
import axios from 'axios';
import crypto from 'crypto';
const app = express();
// OAuth 2.0 configuration
const OAUTH_CONFIG = {
clientId: process.env.OAUTH_CLIENT_ID!,
clientSecret: process.env.OAUTH_CLIENT_SECRET!,
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenEndpoint: 'https://oauth2.googleapis.com/token',
redirectUri: 'http://localhost:3000/callback',
scope: 'openid profile email'
};
// Initiate OAuth flow
app.get('/login', (req, res) => {
const state = crypto.randomBytes(16).toString('hex');
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
// Store state and code verifier in session
req.session.oauthState = state;
req.session.codeVerifier = codeVerifier;
const authUrl = new URL(OAUTH_CONFIG.authorizationEndpoint);
authUrl.searchParams.set('client_id', OAUTH_CONFIG.clientId);
authUrl.searchParams.set('redirect_uri', OAUTH_CONFIG.redirectUri);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', OAUTH_CONFIG.scope);
authUrl.searchParams.set('state', state);
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
res.redirect(authUrl.toString());
});
// Handle OAuth callback
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// Validate state parameter to prevent CSRF
if (state !== req.session.oauthState) {
return res.status(400).send('Invalid state parameter');
}
try {
// Exchange authorization code for access token
const tokenResponse = await axios.post(OAUTH_CONFIG.tokenEndpoint, {
grant_type: 'authorization_code',
code,
redirect_uri: OAUTH_CONFIG.redirectUri,
client_id: OAUTH_CONFIG.clientId,
client_secret: OAUTH_CONFIG.clientSecret,
code_verifier: req.session.codeVerifier
});
const { access_token, refresh_token, id_token } = tokenResponse.data;
// Decode and verify ID token (contains user identity)
const userInfo = await verifyIdToken(id_token);
// Store tokens securely
req.session.accessToken = access_token;
req.session.refreshToken = refresh_token;
req.session.user = userInfo;
res.redirect('/dashboard');
} catch (error) {
console.error('OAuth error:', error);
res.status(500).send('Authentication failed');
}
});
Key security features:
- PKCE (Proof Key for Code Exchange): Protects against authorization code interception
- State parameter: Prevents CSRF attacks
- HTTPS-only: Protects tokens in transit
- Short-lived access tokens: Limits damage from token theft
Token Refresh Flow
Access tokens expire quickly (typically 15-60 minutes). Use refresh tokens to obtain new access tokens without re-authentication:
async function refreshAccessToken(refreshToken: string): Promise<string> {
try {
const response = await axios.post(OAUTH_CONFIG.tokenEndpoint, {
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: OAUTH_CONFIG.clientId,
client_secret: OAUTH_CONFIG.clientSecret
});
return response.data.access_token;
} catch (error) {
// Refresh token expired or revoked - require re-authentication
throw new Error('Refresh token invalid');
}
}
// Middleware to ensure valid access token
async function ensureValidToken(req, res, next) {
const accessToken = req.session.accessToken;
// Check if token is expired (decode JWT and check exp claim)
if (isTokenExpired(accessToken)) {
try {
const newAccessToken = await refreshAccessToken(req.session.refreshToken);
req.session.accessToken = newAccessToken;
} catch (error) {
return res.redirect('/login');
}
}
next();
}
JSON Web Tokens (JWT)
JWTs provide stateless authentication by encoding user identity and claims in a signed token.
JWT Structure and Signing
import jwt from 'jsonwebtoken';
import { promisify } from 'util';
const JWT_SECRET = process.env.JWT_SECRET!;
const JWT_EXPIRES_IN = '15m';
const REFRESH_TOKEN_EXPIRES_IN = '7d';
interface UserPayload {
userId: string;
email: string;
roles: string[];
}
// Generate access token
function generateAccessToken(user: UserPayload): string {
return jwt.sign(
{
sub: user.userId,
email: user.email,
roles: user.roles,
type: 'access'
},
JWT_SECRET,
{
expiresIn: JWT_EXPIRES_IN,
issuer: 'myapp.com',
audience: 'myapp-api'
}
);
}
// Generate refresh token
function generateRefreshToken(user: UserPayload): string {
return jwt.sign(
{
sub: user.userId,
type: 'refresh'
},
JWT_SECRET,
{
expiresIn: REFRESH_TOKEN_EXPIRES_IN,
issuer: 'myapp.com',
audience: 'myapp-api'
}
);
}
// Verify and decode JWT
async function verifyToken(token: string): Promise<any> {
try {
const decoded = await promisify(jwt.verify)(token, JWT_SECRET, {
issuer: 'myapp.com',
audience: 'myapp-api'
});
return decoded;
} catch (error) {
if (error.name === 'TokenExpiredError') {
throw new Error('Token expired');
}
throw new Error('Invalid token');
}
}
// Authentication middleware
async function authenticateJWT(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.substring(7);
try {
const decoded = await verifyToken(token);
if (decoded.type !== 'access') {
return res.status(401).json({ error: 'Invalid token type' });
}
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: error.message });
}
}
JWT Best Practices
Use short expiration times: Access tokens should expire quickly (5-15 minutes) to limit exposure.
Store tokens securely:
- Web: HttpOnly, Secure, SameSite cookies (protects against XSS)
- Mobile: Secure storage (Keychain on iOS, Keystore on Android)
- Never store in localStorage (vulnerable to XSS)
Include minimal claims: Only include necessary information in JWT payload to reduce token size.
Use strong signing algorithms: RS256 (RSA) or ES256 (ECDSA) for production, never HS256 with weak secrets.
Role-Based Access Control (RBAC)
RBAC assigns permissions to roles rather than individual users, simplifying authorization management.
Implementing RBAC
// Database schema (Prisma)
model User {
id String @id @default(uuid())
email String @unique
roles Role[]
}
model Role {
id String @id @default(uuid())
name String @unique
permissions Permission[]
users User[]
}
model Permission {
id String @id @default(uuid())
resource String // e.g., 'posts', 'users', 'settings'
action String // e.g., 'read', 'write', 'delete'
roles Role[]
@@unique([resource, action])
}
// Authorization service
class AuthorizationService {
async hasPermission(
userId: string,
resource: string,
action: string
): Promise<boolean> {
const user = await prisma.user.findUnique({
where: { id: userId },
include: {
roles: {
include: {
permissions: true
}
}
}
});
if (!user) return false;
// Check if any role grants the required permission
return user.roles.some(role =>
role.permissions.some(
p => p.resource === resource && p.action === action
)
);
}
async hasRole(userId: string, roleName: string): Promise<boolean> {
const user = await prisma.user.findUnique({
where: { id: userId },
include: { roles: true }
});
return user?.roles.some(r => r.name === roleName) ?? false;
}
}
// Authorization middleware
function requirePermission(resource: string, action: string) {
return async (req, res, next) => {
const userId = req.user.sub;
const hasPermission = await authService.hasPermission(
userId,
resource,
action
);
if (!hasPermission) {
return res.status(403).json({
error: 'Insufficient permissions',
required: { resource, action }
});
}
next();
};
}
// Usage in routes
app.get('/api/posts',
authenticateJWT,
requirePermission('posts', 'read'),
async (req, res) => {
const posts = await prisma.post.findMany();
res.json(posts);
}
);
app.post('/api/posts',
authenticateJWT,
requirePermission('posts', 'write'),
async (req, res) => {
const post = await prisma.post.create({
data: req.body
});
res.json(post);
}
);
app.delete('/api/posts/:id',
authenticateJWT,
requirePermission('posts', 'delete'),
async (req, res) => {
await prisma.post.delete({
where: { id: req.params.id }
});
res.status(204).send();
}
);
Common RBAC Roles
// Seed default roles and permissions
async function seedRoles() {
// Create permissions
const permissions = await Promise.all([
prisma.permission.create({
data: { resource: 'posts', action: 'read' }
}),
prisma.permission.create({
data: { resource: 'posts', action: 'write' }
}),
prisma.permission.create({
data: { resource: 'posts', action: 'delete' }
}),
prisma.permission.create({
data: { resource: 'users', action: 'read' }
}),
prisma.permission.create({
data: { resource: 'users', action: 'write' }
}),
prisma.permission.create({
data: { resource: 'settings', action: 'write' }
})
]);
// Create roles with associated permissions
await prisma.role.create({
data: {
name: 'admin',
permissions: {
connect: permissions.map(p => ({ id: p.id }))
}
}
});
await prisma.role.create({
data: {
name: 'editor',
permissions: {
connect: permissions
.filter(p => p.resource === 'posts')
.map(p => ({ id: p.id }))
}
}
});
await prisma.role.create({
data: {
name: 'viewer',
permissions: {
connect: permissions
.filter(p => p.action === 'read')
.map(p => ({ id: p.id }))
}
}
});
}
Attribute-Based Access Control (ABAC)
ABAC provides fine-grained authorization based on attributes of the user, resource, and environment.
Implementing ABAC
interface AccessControlContext {
user: {
id: string;
roles: string[];
department: string;
level: number;
};
resource: {
type: string;
id: string;
ownerId: string;
department: string;
sensitivity: 'public' | 'internal' | 'confidential';
};
action: string;
environment: {
time: Date;
ipAddress: string;
userAgent: string;
};
}
class ABACService {
// Policy evaluation
async evaluate(context: AccessControlContext): Promise<boolean> {
const policies = await this.loadPolicies(context.resource.type);
for (const policy of policies) {
if (this.evaluatePolicy(policy, context)) {
return true;
}
}
return false;
}
private evaluatePolicy(policy: Policy, context: AccessControlContext): boolean {
// Evaluate all conditions in the policy
return policy.conditions.every(condition =>
this.evaluateCondition(condition, context)
);
}
private evaluateCondition(condition: Condition, context: AccessControlContext): boolean {
const { attribute, operator, value } = condition;
const actualValue = this.getAttribute(attribute, context);
switch (operator) {
case 'equals':
return actualValue === value;
case 'in':
return Array.isArray(value) && value.includes(actualValue);
case 'greaterThan':
return actualValue > value;
case 'lessThan':
return actualValue < value;
default:
return false;
}
}
private getAttribute(attribute: string, context: AccessControlContext): any {
const [category, field] = attribute.split('.');
switch (category) {
case 'user':
return context.user[field];
case 'resource':
return context.resource[field];
case 'environment':
return context.environment[field];
default:
return undefined;
}
}
}
// Example policies
const documentAccessPolicies = [
{
name: 'Owner can always access',
conditions: [
{ attribute: 'user.id', operator: 'equals', value: 'resource.ownerId' }
]
},
{
name: 'Admins can access all documents',
conditions: [
{ attribute: 'user.roles', operator: 'in', value: ['admin'] }
]
},
{
name: 'Same department can access internal documents',
conditions: [
{ attribute: 'user.department', operator: 'equals', value: 'resource.department' },
{ attribute: 'resource.sensitivity', operator: 'in', value: ['public', 'internal'] }
]
},
{
name: 'Level 3+ can access confidential documents in same department',
conditions: [
{ attribute: 'user.level', operator: 'greaterThan', value: 2 },
{ attribute: 'user.department', operator: 'equals', value: 'resource.department' },
{ attribute: 'resource.sensitivity', operator: 'equals', value: 'confidential' }
]
}
];
// Middleware using ABAC
function requireAccess(resourceType: string) {
return async (req, res, next) => {
const resource = await loadResource(resourceType, req.params.id);
const context: AccessControlContext = {
user: {
id: req.user.sub,
roles: req.user.roles,
department: req.user.department,
level: req.user.level
},
resource: {
type: resourceType,
id: resource.id,
ownerId: resource.ownerId,
department: resource.department,
sensitivity: resource.sensitivity
},
action: req.method,
environment: {
time: new Date(),
ipAddress: req.ip,
userAgent: req.get('user-agent')
}
};
const hasAccess = await abacService.evaluate(context);
if (!hasAccess) {
return res.status(403).json({ error: 'Access denied' });
}
next();
};
}
Session Management
Proper session management prevents session hijacking and fixation attacks.
Secure Session Implementation
import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
// Redis client for session storage
const redisClient = createClient({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || '6379')
});
app.use(
session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
name: 'sessionId', // Don't use default 'connect.sid'
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS only
httpOnly: true, // Prevents JavaScript access
maxAge: 1000 * 60 * 60 * 24, // 24 hours
sameSite: 'strict' // CSRF protection
}
})
);
// Regenerate session ID after login to prevent fixation
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await authenticateUser(email, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Regenerate session ID
req.session.regenerate((err) => {
if (err) {
return res.status(500).json({ error: 'Session error' });
}
req.session.userId = user.id;
req.session.loginTime = Date.now();
res.json({ success: true, user });
});
});
// Destroy session on logout
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: 'Logout failed' });
}
res.clearCookie('sessionId');
res.json({ success: true });
});
});
// Session timeout middleware
function checkSessionTimeout(req, res, next) {
const SESSION_TIMEOUT = 1000 * 60 * 30; // 30 minutes
if (req.session.userId) {
const inactiveTime = Date.now() - (req.session.lastActivity || req.session.loginTime);
if (inactiveTime > SESSION_TIMEOUT) {
req.session.destroy();
return res.status(401).json({ error: 'Session expired' });
}
req.session.lastActivity = Date.now();
}
next();
}
Passwordless Authentication
Passwordless authentication eliminates password-related vulnerabilities while improving user experience.
Magic Link Authentication
import crypto from 'crypto';
import { sendEmail } from './email';
// Generate and store magic link token
async function sendMagicLink(email: string): Promise<void> {
const token = crypto.randomBytes(32).toString('hex');
const expires = new Date(Date.now() + 1000 * 60 * 15); // 15 minutes
// Store token in database
await prisma.magicLinkToken.create({
data: {
token,
email,
expires
}
});
const magicLink = ${process.env.APP_URL}/auth/verify?token=${token};
await sendEmail({
to: email,
subject: 'Sign in to MyApp',
html: <p>Click the link below to sign in:</p> <a href="${magicLink}">${magicLink}</a> <p>This link expires in 15 minutes.</p>
});
}
// Verify magic link token
app.get('/auth/verify', async (req, res) => {
const { token } = req.query;
const magicLink = await prisma.magicLinkToken.findUnique({
where: { token: token as string }
});
if (!magicLink) {
return res.status(400).send('Invalid token');
}
if (new Date() > magicLink.expires) {
await prisma.magicLinkToken.delete({
where: { id: magicLink.id }
});
return res.status(400).send('Token expired');
}
// Find or create user
let user = await prisma.user.findUnique({
where: { email: magicLink.email }
});
if (!user) {
user = await prisma.user.create({
data: { email: magicLink.email }
});
}
// Delete used token
await prisma.magicLinkToken.delete({
where: { id: magicLink.id }
});
// Create session
req.session.regenerate((err) => {
if (err) {
return res.status(500).send('Authentication failed');
}
req.session.userId = user.id;
res.redirect('/dashboard');
});
});
WebAuthn (Passkeys)
import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server';
// Initiate passkey registration
app.post('/auth/register/begin', async (req, res) => {
const { email } = req.body;
const user = await prisma.user.findUnique({
where: { email }
});
const options = await generateRegistrationOptions({
rpName: 'MyApp',
rpID: 'myapp.com',
userID: user.id,
userName: user.email,
attestationType: 'none',
authenticatorSelection: {
residentKey: 'preferred',
userVerification: 'preferred'
}
});
// Store challenge for verification
req.session.challenge = options.challenge;
res.json(options);
});
// Complete passkey registration
app.post('/auth/register/complete', async (req, res) => {
const { credential } = req.body;
const verification = await verifyRegistrationResponse({
response: credential,
expectedChallenge: req.session.challenge,
expectedOrigin: 'https://myapp.com',
expectedRPID: 'myapp.com'
});
if (!verification.verified) {
return res.status(400).json({ error: 'Verification failed' });
}
// Store credential for future authentication
await prisma.passkey.create({
data: {
userId: req.user.id,
credentialId: verification.registrationInfo.credentialID,
publicKey: verification.registrationInfo.credentialPublicKey,
counter: verification.registrationInfo.counter
}
});
res.json({ success: true });
});
Multi-Factor Authentication (MFA)
MFA adds an extra layer of security beyond passwords.
Time-Based One-Time Passwords (TOTP)
import speakeasy from 'speakeasy';
import QRCode from 'qrcode';
// Enable TOTP MFA
app.post('/auth/mfa/enable', authenticateJWT, async (req, res) => {
const secret = speakeasy.generateSecret({
name: MyApp (${req.user.email}),
issuer: 'MyApp'
});
// Generate QR code for authenticator app
const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
// Store secret temporarily until verified
req.session.pendingMfaSecret = secret.base32;
res.json({
qrCode: qrCodeUrl,
secret: secret.base32
});
});
// Verify and activate TOTP
app.post('/auth/mfa/verify', authenticateJWT, async (req, res) => {
const { token } = req.body;
const secret = req.session.pendingMfaSecret;
const verified = speakeasy.totp.verify({
secret,
encoding: 'base32',
token,
window: 2 // Allow 2 time steps for clock drift
});
if (!verified) {
return res.status(400).json({ error: 'Invalid token' });
}
// Save MFA secret to user
await prisma.user.update({
where: { id: req.user.sub },
data: {
mfaSecret: secret,
mfaEnabled: true
}
});
delete req.session.pendingMfaSecret;
res.json({ success: true });
});
// Verify TOTP during login
app.post('/auth/login', async (req, res) => {
const { email, password, totpToken } = req.body;
const user = await authenticateUser(email, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Check if MFA is enabled
if (user.mfaEnabled) {
if (!totpToken) {
return res.status(200).json({ mfaRequired: true });
}
const verified = speakeasy.totp.verify({
secret: user.mfaSecret,
encoding: 'base32',
token: totpToken,
window: 2
});
if (!verified) {
return res.status(401).json({ error: 'Invalid MFA token' });
}
}
// Create session
const accessToken = generateAccessToken(user);
const refreshToken = generateRefreshToken(user);
res.json({ accessToken, refreshToken });
});
API Key Authentication
API keys provide simple authentication for service-to-service communication.
Implementing API Keys
import crypto from 'crypto';
// Generate API key
app.post('/api/keys', authenticateJWT, async (req, res) => {
const { name, scopes } = req.body;
// Generate cryptographically secure key
const key = sk_${crypto.randomBytes(32).toString('hex')};
const hashedKey = await hashApiKey(key);
const apiKey = await prisma.apiKey.create({
data: {
name,
keyHash: hashedKey,
userId: req.user.sub,
scopes: scopes || []
}
});
// Return key only once - cannot be retrieved later
res.json({
id: apiKey.id,
key, // Show only on creation
name: apiKey.name,
scopes: apiKey.scopes
});
});
async function hashApiKey(key: string): Promise<string> {
return crypto
.createHash('sha256')
.update(key)
.digest('hex');
}
// API key authentication middleware
async function authenticateApiKey(req, res, next) {
const apiKey = req.headers['x-api-key'];
if (!apiKey || typeof apiKey !== 'string') {
return res.status(401).json({ error: 'API key required' });
}
const hashedKey = await hashApiKey(apiKey);
const key = await prisma.apiKey.findUnique({
where: { keyHash: hashedKey },
include: { user: true }
});
if (!key) {
return res.status(401).json({ error: 'Invalid API key' });
}
if (!key.active) {
return res.status(401).json({ error: 'API key revoked' });
}
// Update last used timestamp
await prisma.apiKey.update({
where: { id: key.id },
data: { lastUsedAt: new Date() }
});
req.user = key.user;
req.apiKeyScopes = key.scopes;
next();
}
// Scope validation middleware
function requireScope(scope: string) {
return (req, res, next) => {
if (!req.apiKeyScopes || !req.apiKeyScopes.includes(scope)) {
return res.status(403).json({
error: 'Insufficient scope',
required: scope
});
}
next();
};
}
// Usage
app.get('/api/data',
authenticateApiKey,
requireScope('data:read'),
async (req, res) => {
// Handle request
}
);
Security Best Practices
Rate Limiting
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
// Authentication endpoint rate limiting
const authLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'rl:auth:'
}),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts per window
message: 'Too many login attempts, please try again later',
standardHeaders: true,
legacyHeaders: false
});
app.post('/auth/login', authLimiter, async (req, res) => {
// Handle login
});
// API rate limiting
const apiLimiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'rl:api:'
}),
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 requests per minute
keyGenerator: (req) => {
// Rate limit by API key or user ID
return req.apiKey?.id || req.user?.sub || req.ip;
}
});
app.use('/api', apiLimiter);
Password Hashing
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12;
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
// Password strength validation
function isStrongPassword(password: string): boolean {
const minLength = 12;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
return (
password.length >= minLength &&
hasUpperCase &&
hasLowerCase &&
hasNumbers &&
hasSpecialChar
);
}
Token Blacklisting
// Blacklist tokens on logout or password change
async function blacklistToken(token: string): Promise<void> {
const decoded = jwt.decode(token) as any;
const expiresIn = decoded.exp - Math.floor(Date.now() / 1000);
// Store in Redis with TTL matching token expiration
await redisClient.setEx(
blacklist:${token},
expiresIn,
'revoked'
);
}
// Check blacklist in authentication middleware
async function authenticateJWT(req, res, next) {
const token = extractToken(req);
// Check if token is blacklisted
const isBlacklisted = await redisClient.exists(blacklist:${token});
if (isBlacklisted) {
return res.status(401).json({ error: 'Token revoked' });
}
// Verify token
try {
const decoded = await verifyToken(token);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: error.message });
}
}
Real-World Examples
GitHub's Authentication
GitHub uses multiple authentication methods:
- OAuth Apps: For third-party integrations
- Personal Access Tokens: Fine-grained permissions with 1-year expiration
- SSH Keys: For Git operations
- SAML SSO: For enterprise organizations
Their OAuth implementation supports the authorization code flow with PKCE and enforces short-lived access tokens (8 hours) with refresh tokens for extended access.
Auth0's Platform
Auth0 provides authentication as a service handling:
- Universal Login: Centralized login page preventing phishing
- Anomaly Detection: Identifies suspicious login patterns (impossible travel, brute force)
- Adaptive MFA: Requires additional authentication for risky logins
- Session Management: Configurable timeouts and concurrent session limits
Auth0 processes over 2.5 billion logins monthly with 99.99% uptime SLA.
Stripe's API Authentication
Stripe uses API keys with distinct prefixes:
pk_: Publishable keys (client-side, public)sk_: Secret keys (server-side only)rk_: Restricted keys (limited permissions)
Keys are scoped to test or live environments, preventing accidental production charges during development. Stripe also implements automatic key rotation and provides detailed audit logs of API key usage.
Conclusion
Authentication and authorization form the security foundation of modern applications. OAuth 2.0 and OIDC provide industry-standard authentication flows, while JWTs enable stateless authorization. Implement RBAC for simple permission models or ABAC for fine-grained control based on attributes. Enhance security with MFA, passwordless authentication, and proper session management.
Following these production patterns - along with rate limiting, secure password hashing, and token management - creates a robust authentication system that protects user data while maintaining excellent user experience. Regular security audits and staying updated on emerging threats ensure your authentication implementation remains secure over time.
Related Articles
GraphQL API Design - Production Architecture and Best Practices for Scalable Systems
Master GraphQL API design covering schema design principles, resolver optimization, N+1 query prevention with DataLoader, authentication and authorization patterns, caching strategies, error handling, and production deployment for high-performance GraphQL systems.
Testing Strategies - Unit, Integration, and E2E Testing Best Practices for Production Quality
Comprehensive guide to testing strategies covering unit tests, integration tests, end-to-end testing, test-driven development, mocking patterns, testing pyramid, and production testing practices for reliable software delivery.
Monitoring and Observability - Production Systems Performance and Debugging at Scale
Master monitoring and observability covering metrics collection with Prometheus, distributed tracing with OpenTelemetry, log aggregation, alerting strategies, SLOs/SLIs, and production debugging techniques for reliable systems.
Written by StaticBlock Editorial
StaticBlock Editorial is a technical writer and software engineer specializing in web development, performance optimization, and developer tooling.