Skip to content

Security — Theory

  • AuthN — who you are. Done once per session/token.
  • AuthZ — what you can do. Done on every request, per resource.

Confusing them is a common bug source.

OAuth 2.0 / OIDC flow (Authorization Code + PKCE)

Section titled “OAuth 2.0 / OIDC flow (Authorization Code + PKCE)”
  1. Client redirects user to AS with code_challenge = SHA256(code_verifier).
  2. User authenticates at AS, grants scopes.
  3. AS redirects to client with authorization code.
  4. Client POSTs code + code_verifier to AS token endpoint.
  5. AS verifies challenge matches, issues access token (and refresh + id_token if OIDC).
  6. Client uses access token in Authorization: Bearer <token> to call resource server.

PKCE prevents code interception — even if attacker grabs the code mid-flight, they don’t have the verifier.

JWTOpaque
Validationself-contained, cryptographicrequires AS lookup or introspection
Revocationhard (denylist + short TTL)easy (delete)
Sizelarger (every request)small
Privacyclaims visible to anyone with tokenonly AS knows
Distributed verifyyes — give pub keyno — call AS

For internal microservices: JWT signed by central IdP, public key distributed. For sessions on a single product: opaque session id + DB.

  • Issue new session id on auth boundary changes (login, privilege escalation).
  • Bind to client fingerprint (UA + IP) to limit theft (but allow legitimate roaming).
  • Short idle timeout + absolute max lifetime.
  • Logout clears server-side state.
  • Concurrent session limits if business demands.

If the browser sends cookies automatically, an attacker site can trigger requests on user’s behalf.

Mitigations (combine 2+):

  • SameSite=Lax cookies — blocks most cross-origin POSTs.
  • CSRF token — server-issued random value tied to session, sent in form/header, verified.
  • Double-submit cookie — token in cookie + matching header; server compares.
  • Re-authenticate for sensitive ops.

JWTs in Authorization header avoid CSRF entirely (browser doesn’t auto-attach), but introduce XSS risk if stored in localStorage.

Attacker injects script into page → runs as victim.

Types:

  • Stored — saved in DB, served back.
  • Reflected — in URL, echoed in response.
  • DOM-based — JS reads location.hash etc.

Mitigations:

  • Output escape per context (HTML attr ≠ HTML body ≠ JS string ≠ URL).
  • Frameworks (React, Vue, Svelte) auto-escape — don’t bypass via dangerouslySetInnerHTML.
  • Content Security Policy (CSP): restrict script sources, eval, inline scripts.
  • HttpOnly cookies so XSS can’t steal sessions.

Always parameterize. ORMs help but raw queries still risky.

// BAD
db.query(`SELECT * FROM u WHERE email = '${email}'`);
// GOOD
db.query(`SELECT * FROM u WHERE email = $1`, [email]);

Stored procedures don’t automatically protect — concat inside SP is vulnerable too.

App fetches user-provided URL → attacker points to internal services / cloud metadata (http://169.254.169.254/).

Mitigations:

  • Block private IP ranges (10/8, 172.16/12, 192.168/16, 169.254/16, 127/8) at app + DNS resolution time.
  • Deny redirects to internal addresses.
  • Allowlist domains for outbound calls.
  • Use IMDSv2 (AWS) — requires session token, breaks naive SSRF.

Layers:

  • Per-IP at edge (Cloudflare, nginx).
  • Per-user / per-API-key at app.
  • Per-endpoint (login throttling).
  • Cost-based (GraphQL complexity).

Algorithms: fixed window, sliding window, token bucket, leaky bucket. Use Redis counters or service mesh.

  • In transit: TLS everywhere. Internal too (mTLS via mesh).
  • At rest: DB-level (TDE), volume-level (LUKS, EBS encryption), or app-level field encryption (most sensitive). Key management via KMS — envelope encryption (DEK encrypted by KEK).
  • End-to-end: client encrypts, server only sees ciphertext (Signal, Proton).
  • Don’t store master key in env or repo.
  • Use KMS / Vault → app gets short-lived credential or wrapped key.
  • Rotate keys; old ciphertext decryptable with old key during rotation window.
  • Audit access to KMS.
  1. JWT vs sessions — when? As above. Internal microservices favor JWT. User-facing dashboards often opaque sessions.
  2. Where do you store JWT in browser? Trade-off: cookie (CSRF risk, immune to XSS) vs localStorage (XSS risk, immune to CSRF). HttpOnly cookie + CSRF mitigations is safer.
  3. What is PKCE and why? Prevents code interception in public clients.
  4. How to revoke a JWT before exp? Denylist (jti checked in Redis), or rotate signing key (broad). Short TTL minimizes impact.
  5. Difference between hashing and encryption? Hash one-way, encrypt reversible.
  6. bcrypt vs argon2? Both good. Argon2id resists GPU/ASIC better.
  7. Explain a CSRF attack. Cross-origin POST relies on cookies. Defense: SameSite, anti-CSRF token.
  8. Explain SSRF and metadata service exploit. Server fetches attacker URL pointing to 169.254.169.254 to read IAM creds.
  9. What is mass assignment? Binding all input fields to model. Allow-list explicit fields.
  10. Best practices for secret rotation? Automated, via Vault/Secrets Manager, dual-version window, observability.
  11. What is mTLS, and where would you use it? Both ends present cert. Service mesh, gateway-to-backend, gRPC internal.
  12. OAuth scopes vs roles? Scopes coarse (often delegated by user); roles fine-grained internal.

For each component, ask:

  • Spoofing — pretending to be someone else.
  • Tampering — modifying data.
  • Repudiation — denying an action happened.
  • Information disclosure — leaking data.
  • Denial of service.
  • Elevation of privilege.

Drives both design and tests.

Old: trust inside the network. New: every request authenticated and authorized regardless of origin.

Implies:

  • mTLS everywhere.
  • Strong workload identity (SPIFFE/SPIRE).
  • Policy enforcement at every hop.
  • No implicit network trust.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-...'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Permissions-Policy: camera=(), microphone=()