Safety

Authentication & Authorization

Verifying identity and enforcing what authenticated users are permitted to do.

Overview

Authentication verifies who a user is; authorisation determines what they can do. Modern web authentication is predominantly token-based (JWT, opaque tokens) or session-based (server-side session store). OAuth 2.0 and OpenID Connect (OIDC) are the standards for federated identity and SSO. Passwords, when used, must be hashed with bcrypt, argon2id, or scrypt, never MD5 or SHA-1.

Origin

Password-based authentication predates the internet; Unix /etc/passwd dates to 1969. OAuth 1.0 (2007) and 2.0 (2012, RFC 6749) standardised delegated authorisation. OpenID Connect (2014) added an identity layer on top of OAuth 2.0. FIDO2/WebAuthn (2019, W3C standard) introduced hardware-key and biometric authentication as a phishing-resistant alternative to passwords.

Examples

JWT authentication middleware in TypeScript

import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';

interface AuthPayload {
  sub: string;
  email: string;
  roles: string[];
  iat: number;
  exp: number;
}

export function requireAuth(req: Request, res: Response, next: NextFunction): void {
  const header = req.headers['authorization'];
  if (!header?.startsWith('Bearer ')) {
    res.status(401).json({ error: 'Missing or invalid Authorization header' });
    return;
  }

  const token = header.slice(7);
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!, {
      algorithms: ['HS256'],
      issuer: 'https://auth.example.com',
      audience: 'https://api.example.com',
    }) as AuthPayload;
    (req as any).user = payload;
    next();
  } catch (err) {
    if (err instanceof jwt.TokenExpiredError) {
      res.status(401).json({ error: 'Token expired' });
    } else {
      res.status(401).json({ error: 'Invalid token' });
    }
  }
}

Always verify algorithms explicitly; if omitted, an attacker can forge tokens using the "alg: none" vulnerability (CVE-2015-9235). iss and aud claims bind the token to a specific issuer and intended audience, preventing token reuse across services.

Password hashing with bcrypt in Ruby

require 'bcrypt'

class User < ApplicationRecord
  MINIMUM_PASSWORD_LENGTH = 12

  validates :email, presence: true, uniqueness: { case_sensitive: false }
  validates :password, length: { minimum: MINIMUM_PASSWORD_LENGTH }, if: :password_digest_changed?

  def password=(plaintext)
    raise ArgumentError, 'Password too short' if plaintext.length < MINIMUM_PASSWORD_LENGTH
    self.password_digest = BCrypt::Password.create(plaintext, cost: 12)
  end

  def authenticate(plaintext)
    BCrypt::Password.new(password_digest).is_password?(plaintext)
  end

  def self.find_and_authenticate(email, plaintext)
    user = find_by(email: email.downcase.strip)
    # Always run bcrypt even for unknown users to prevent timing attacks
    dummy_hash = BCrypt::Password.create('dummy', cost: 12)
    return nil unless user
    user.authenticate(plaintext) ? user : nil
  end
end

Cost factor 12 is ~300ms on a modern CPU, making brute-force impractical. The dummy hash for unknown users prevents timing-based user enumeration: an attacker cannot distinguish "user not found" from "wrong password" via response time differences.

Use Cases

  • 01API authentication via short-lived access tokens (15 minutes) with longer-lived refresh tokens (7 days) stored in HttpOnly cookies
  • 02SSO via OIDC (Auth0, Okta, Keycloak) where the application delegates identity to a trusted provider
  • 03Service-to-service authentication via mTLS or signed JWT tokens with short expiry and no refresh cycle
  • 04WebAuthn/passkey authentication for consumer applications where phishing-resistant credential management is required

When Not to Use

  • //Do not roll your own cryptographic primitives; use bcrypt, argon2id, or scrypt via a well-maintained library
  • //Do not store JWTs in localStorage; they are accessible to JavaScript and vulnerable to XSS. Use HttpOnly Secure SameSite=Strict cookies
  • //Do not use long-lived JWT tokens without a revocation mechanism; a leaked token is valid until expiry. Implement a blocklist or use short-lived tokens with refresh

Technical Notes

  • argon2id is the Password Hashing Competition (PHC, 2015) winner and NIST-recommended (SP 800-63B) over bcrypt for new systems; it is resistant to GPU cracking via memory-hard parameters
  • JWT structure: header.payload.signature, all base64url-encoded. The signature covers header and payload only; a JWT can be decoded by anyone. Never put sensitive data in JWT claims without encrypting the token (JWE)
  • OAuth 2.0 PKCE (Proof Key for Code Exchange, RFC 7636) is required for public clients (SPAs, mobile apps) where the client secret cannot be kept confidential; it prevents authorisation code interception attacks
  • Refresh token rotation (each use issues a new refresh token and invalidates the old one) limits the damage window of a stolen refresh token and enables detection of token theft via replay