Web-security
02 / 02

Auth Patterns & Secure Coding

Auth Patterns & Secure Coding

JWT Security

// JWT best practices
// 1. Use RS256 (asymmetric) for multi-service environments
// 2. Short expiry for access tokens (15 min)
// 3. Store refresh token in HttpOnly cookie, access token in memory
// 4. Validate: signature, expiry, issuer, audience
// 5. Include jti (JWT ID) for revocation

import jwt from 'jsonwebtoken';

function generateTokens(userId: string) {
  const accessToken = jwt.sign(
    { sub: userId, type: 'access' },
    process.env.JWT_SECRET!,
    { expiresIn: '15m', issuer: 'myapp', audience: 'myapp-api' }
  );
  const refreshToken = jwt.sign(
    { sub: userId, type: 'refresh', jti: crypto.randomUUID() },
    process.env.JWT_REFRESH_SECRET!,
    { expiresIn: '7d' }
  );
  return { accessToken, refreshToken };
}

function verifyToken(token: string) {
  return jwt.verify(token, process.env.JWT_SECRET!, {
    issuer: 'myapp',
    audience: 'myapp-api',
  });
}

Password Hashing

import bcrypt from 'bcrypt';

const SALT_ROUNDS = 12;  // balance security vs speed (10-14 typical)

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);
}

// Argon2 (preferred, winner of Password Hashing Competition)
import argon2 from 'argon2';
const hash = await argon2.hash(password);
const valid = await argon2.verify(hash, password);

// Timing-safe comparison (prevent timing attacks)
import crypto from 'crypto';
function safeCompare(a: string, b: string): boolean {
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
}

Rate Limiting & DoS Prevention

import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';

// General API rate limit
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 100,
  message: { error: 'Too many requests, please try again later.' },
  standardHeaders: true,      // X-RateLimit-* headers
  legacyHeaders: false,
});

// Stricter limit for auth endpoints
const authLimiter = rateLimit({
  windowMs: 60 * 1000,       // 1 minute
  max: 5,                    // 5 attempts per minute
  skipSuccessfulRequests: true,
});

app.use('/api/', apiLimiter);
app.post('/auth/login', authLimiter, loginHandler);

// Additional protections
app.use(express.json({ limit: '10kb' }));  // prevent large payload attacks
app.use(helmet());                           // security headers
app.disable('x-powered-by');               // don't advertise Express

Secure Development Checklist

  • Scan dependencies for vulnerabilities (npm audit, Snyk, Dependabot)

  • Never log sensitive data (passwords, tokens, PII, credit card numbers)

  • Validate and sanitize ALL inputs at the API boundary

  • Use HTTPS for all communications (internal services too)

  • Secret rotation — rotate DB passwords, API keys periodically

  • Principle of least privilege — services/users get only what they need

  • Security code reviews — look for OWASP top 10 in PRs

  • Penetration testing — at least annually for production systems

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free