Authentication vs Authorization
Authentication verifies identity (who you are). Authorization determines permissions (what you can do). Modern systems need both.
OAuth 2.0 Flows
Authorization Code Flow (Web Apps)
// Step 1: Redirect to authorization server
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('redirect_uri', 'https://app.example.com/callback');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('state', generateState());
window.location.href = authUrl.toString();
// Step 2: Exchange code for tokens (server-side)
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state to prevent CSRF
if (state !== req.session.state) {
return res.status(400).send('Invalid state');
}
const tokens = await fetch('https://auth.example.com/token', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: 'https://app.example.com/callback',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET
})
}).then(r => r.json());
req.session.tokens = tokens;
res.redirect('/dashboard');
});Authorization Code with PKCE (SPAs, Mobile)
// Generate PKCE challenge
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64UrlEncode(array);
}
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return base64UrlEncode(new Uint8Array(hash));
}
// Include in auth request
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
// Store verifier for token exchange
sessionStorage.setItem('code_verifier', codeVerifier);OpenID Connect
ID Token Validation
import * as jose from 'jose';
async function validateIdToken(idToken) {
const JWKS = jose.createRemoteJWKSet(
new URL('https://auth.example.com/.well-known/jwks.json')
);
const { payload } = await jose.jwtVerify(idToken, JWKS, {
issuer: 'https://auth.example.com',
audience: CLIENT_ID
});
// Additional validations
if (payload.nonce !== expectedNonce) {
throw new Error('Invalid nonce');
}
if (payload.exp < Date.now() / 1000) {
throw new Error('Token expired');
}
return payload;
}Session Management
// Secure session configuration
import session from 'express-session';
import RedisStore from 'connect-redis';
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
name: '__Host-session', // Cookie prefix for security
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JS access
sameSite: 'strict', // CSRF protection
maxAge: 3600000 // 1 hour
}
}));Token Refresh
async function refreshTokens(refreshToken) {
const response = await fetch('https://auth.example.com/token', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: CLIENT_ID
})
});
if (!response.ok) {
throw new Error('Token refresh failed');
}
return response.json();
}
// Automatic refresh middleware
async function ensureValidToken(req, res, next) {
const tokens = req.session.tokens;
if (isTokenExpired(tokens.access_token)) {
try {
req.session.tokens = await refreshTokens(tokens.refresh_token);
} catch (error) {
return res.redirect('/login');
}
}
next();
}Passwordless Authentication
WebAuthn / Passkeys
// Registration
async function registerPasskey() {
const options = await fetch('/auth/webauthn/register-options').then(r => r.json());
const credential = await navigator.credentials.create({
publicKey: {
challenge: Uint8Array.from(options.challenge),
rp: { name: 'My App', id: 'app.example.com' },
user: {
id: Uint8Array.from(options.userId),
name: options.username,
displayName: options.displayName
},
pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
authenticatorSelection: {
authenticatorAttachment: 'platform',
userVerification: 'required'
}
}
});
await fetch('/auth/webauthn/register', {
method: 'POST',
body: JSON.stringify(credential)
});
}Multi-Factor Authentication
import { authenticator } from 'otplib';
// Generate TOTP secret
const secret = authenticator.generateSecret();
const otpauth = authenticator.keyuri(user.email, 'MyApp', secret);
// Verify TOTP code
function verifyTOTP(secret, token) {
return authenticator.verify({ token, secret });
}Conclusion
Choose the right authentication pattern for your use case. OAuth 2.0 with PKCE for SPAs, server-side tokens for web apps, and consider passwordless for better UX and security.