Files
english/.opencode/skills/backend-development/references/backend-authentication.md
2026-04-12 01:06:31 +07:00

9.2 KiB

Backend Authentication & Authorization

Modern authentication patterns including OAuth 2.1, JWT, RBAC, and MFA (2025 standards).

OAuth 2.1 (2025 Standard)

Key Changes from OAuth 2.0

Mandatory:

  • PKCE (Proof Key for Code Exchange) for all clients
  • Exact redirect URI matching
  • State parameter for CSRF protection

Deprecated:

  • Implicit grant flow (security risk)
  • Resource owner password credentials grant
  • Bearer token in query strings

Authorization Code Flow with PKCE

// Step 1: Generate code verifier and challenge
import crypto from 'crypto';

const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
  .createHash('sha256')
  .update(codeVerifier)
  .digest('base64url');

// Step 2: Redirect to authorization endpoint
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('client_id', 'your-client-id');
authUrl.searchParams.set('redirect_uri', 'https://app.example.com/callback');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('state', crypto.randomBytes(16).toString('hex'));
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');

// Step 3: Exchange code for token (with code_verifier)
const tokenResponse = await fetch('https://auth.example.com/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authCode,
    redirect_uri: redirectUri,
    client_id: clientId,
    code_verifier: codeVerifier,
  }),
});

JWT (JSON Web Tokens)

Structure

Header.Payload.Signature
eyJhbGciOi...  .  eyJzdWIiOi...  .  SflKxwRJ...

Best Practices (2025)

  1. Short expiration - Access tokens: 15 minutes, Refresh tokens: 7 days
  2. Use RS256 - Asymmetric signing (not HS256 for public APIs)
  3. Validate everything - Signature, issuer, audience, expiration
  4. Include minimal claims - Don't include sensitive data
  5. Refresh token rotation - Issue new refresh token on each use

Implementation

import jwt from 'jsonwebtoken';

// Generate JWT
const accessToken = jwt.sign(
  {
    sub: user.id,
    email: user.email,
    roles: user.roles,
  },
  process.env.JWT_PRIVATE_KEY,
  {
    algorithm: 'RS256',
    expiresIn: '15m',
    issuer: 'https://api.example.com',
    audience: 'https://app.example.com',
  }
);

// Verify JWT
const decoded = jwt.verify(token, process.env.JWT_PUBLIC_KEY, {
  algorithms: ['RS256'],
  issuer: 'https://api.example.com',
  audience: 'https://app.example.com',
});

Role-Based Access Control (RBAC)

RBAC Model

Users → Roles → Permissions → Resources

Implementation (NestJS Example)

// Define roles
export enum Role {
  ADMIN = 'admin',
  EDITOR = 'editor',
  VIEWER = 'viewer',
}

// Role decorator
export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);

// Guard implementation
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.get<Role[]>('roles', context.getHandler());
    if (!requiredRoles) return true;

    const request = context.switchToHttp().getRequest();
    const user = request.user;

    return requiredRoles.some((role) => user.roles?.includes(role));
  }
}

// Usage
@Post()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMIN, Role.EDITOR)
async createPost(@Body() createPostDto: CreatePostDto) {
  return this.postsService.create(createPostDto);
}

RBAC Best Practices

  1. Deny by default - Explicitly grant permissions
  2. Least privilege - Minimum permissions needed
  3. Role hierarchy - Admin inherits Editor inherits Viewer
  4. Separate roles and permissions - Flexible permission assignment
  5. Audit trail - Log role changes and access

Multi-Factor Authentication (MFA)

TOTP (Time-Based One-Time Password)

import speakeasy from 'speakeasy';
import QRCode from 'qrcode';

// Generate secret
const secret = speakeasy.generateSecret({
  name: 'MyApp',
  issuer: 'MyCompany',
});

// Generate QR code for user
const qrCode = await QRCode.toDataURL(secret.otpauth_url);

// Verify TOTP token
const verified = speakeasy.totp.verify({
  secret: secret.base32,
  encoding: 'base32',
  token: userToken,
  window: 2, // Allow 2 time steps drift
});

FIDO2/WebAuthn (Passwordless - 2025 Standard)

Benefits:

  • Phishing-resistant
  • No shared secrets
  • Hardware-backed security
  • Better UX (biometrics, security keys)

Implementation:

// Registration
const publicKeyCredentialCreationOptions = {
  challenge: crypto.randomBytes(32),
  rp: { name: 'MyApp', id: 'example.com' },
  user: {
    id: Buffer.from(user.id),
    name: user.email,
    displayName: user.name,
  },
  pubKeyCredParams: [{ alg: -7, type: 'public-key' }], // ES256
  authenticatorSelection: {
    authenticatorAttachment: 'platform', // 'platform' or 'cross-platform'
    userVerification: 'required',
  },
  timeout: 60000,
  attestation: 'direct',
};

// Use @simplewebauthn/server library
import { verifyRegistrationResponse, verifyAuthenticationResponse } from '@simplewebauthn/server';

Session Management

Best Practices

  1. Secure cookies - HttpOnly, Secure, SameSite=Strict
  2. Session timeout - Idle: 15 minutes, Absolute: 8 hours
  3. Regenerate session ID - After login, privilege elevation
  4. Server-side storage - Redis for distributed systems
  5. CSRF protection - SameSite cookies + CSRF tokens

Implementation

import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';

const redisClient = createClient();
await redisClient.connect();

app.use(
  session({
    store: new RedisStore({ client: redisClient }),
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
      secure: true, // HTTPS only
      httpOnly: true, // No JavaScript access
      sameSite: 'strict', // CSRF protection
      maxAge: 1000 * 60 * 15, // 15 minutes
    },
  })
);

Password Security

Argon2id (2025 Standard - Replaces bcrypt)

Why Argon2id:

  • Winner of Password Hashing Competition (2015)
  • Memory-hard (resistant to GPU/ASIC attacks)
  • Configurable CPU and memory cost
  • Combines Argon2i (data-independent) + Argon2d (data-dependent)
import argon2 from 'argon2';

// Hash password
const hash = await argon2.hash('password123', {
  type: argon2.argon2id,
  memoryCost: 65536, // 64 MB
  timeCost: 3, // 3 iterations
  parallelism: 4, // 4 threads
});

// Verify password
const valid = await argon2.verify(hash, 'password123');

Password Policy (2025 NIST Guidelines)

  • Minimum length: 12 characters (not 8)
  • No composition rules - Allow passphrases
  • Check against breach databases - HaveIBeenPwned API
  • No periodic rotation - Only on compromise
  • Allow all printable characters - Including spaces, emojis

API Key Authentication

Best Practices

  1. Prefix keys - sk_live_, pk_test_ (identify type/environment)
  2. Hash stored keys - Store SHA-256 hash, not plaintext
  3. Key rotation - Allow users to rotate keys
  4. Scope limiting - Separate keys for read/write operations
  5. Rate limiting - Per API key limits
// Generate API key
const apiKey = `sk_${env}_${crypto.randomBytes(24).toString('base64url')}`;

// Store hashed version
const hashedKey = crypto.createHash('sha256').update(apiKey).digest('hex');
await db.apiKeys.create({ userId, hashedKey, scopes: ['read'] });

// Validate API key
const providedHash = crypto.createHash('sha256').update(providedKey).digest('hex');
const keyRecord = await db.apiKeys.findOne({ hashedKey: providedHash });

Authentication Decision Matrix

Use Case Recommended Approach
Web application OAuth 2.1 + JWT
Mobile app OAuth 2.1 + PKCE
SPA (Single Page App) OAuth 2.1 Authorization Code + PKCE
Server-to-server Client credentials grant + mTLS
Third-party API access API keys with scopes
High-security WebAuthn/FIDO2 + MFA
Internal admin JWT + RBAC + MFA
Microservices Service mesh (mTLS) + JWT

Security Checklist

  • OAuth 2.1 with PKCE implemented
  • JWT tokens expire in 15 minutes
  • Refresh token rotation enabled
  • RBAC with deny-by-default
  • MFA required for admin accounts
  • Passwords hashed with Argon2id
  • Session cookies: HttpOnly, Secure, SameSite
  • Rate limiting on auth endpoints (10 attempts/15 min)
  • Account lockout after failed attempts
  • Password policy: 12+ chars, breach check
  • Audit logging for authentication events

Resources