{}The Interview
Handbook

Tracks / Security

Application security & auth

senior 10 questions · 7 min read securityowaspauthjwt

Questions in this set 10
  1. 01Explain SQL injection and how to prevent it properly.
  2. 02What is XSS, what types are there, and how do you prevent it?
  3. 03Explain CSRF and its modern defences.
  4. 04How do you store passwords?
  5. 05Sessions vs JWTs — which do you choose, and why?
  6. 06Explain OAuth 2.0 and OIDC at a level you could implement.
  7. 07What is the difference between authentication and authorisation, and how do you model permissions?
  8. 08What is in the OWASP Top 10, and which do you see most in review?
  9. 09What is SSRF and how do you prevent it?
  10. 10How do you handle secrets and dependency risk?
01

Explain SQL injection and how to prevent it properly.

Injection happens when untrusted input becomes part of a command rather than data.

python
cur.execute(f"SELECT * FROM users WHERE email = '{email}'")   # vulnerable
cur.execute("SELECT * FROM users WHERE email = %s", [email])  # parameter binding — safe

The only real fix is parameterised queries / prepared statements, where the driver sends the query and the values separately so the value can never be parsed as SQL. Escaping by hand is a losing game (encodings, multi-byte characters, second-order injection through stored data).

What to add for a senior answer: identifiers (table and column names) cannot be parameterised, so a dynamic ORDER BY must come from an allow-list, never from user input. Use least-privilege database accounts so a successful injection cannot DROP. And ORMs are safe by default but raw(), text() and .extra() with f-strings put you right back in danger.

02

What is XSS, what types are there, and how do you prevent it?

Cross-site scripting is executing attacker-controlled script in your origin, which means reading the DOM, exfiltrating tokens and acting as the user.

  • Stored — the payload is persisted (a comment) and served to every viewer.
  • Reflected — echoed from the request (a search term in the results page).
  • DOM-based — never touches the server; client code writes untrusted data into a sink like innerHTML, document.write or eval.

Defences, layered: contextual output encoding (HTML body, attribute, JS, URL and CSS contexts each need different escaping — templating engines with autoescape handle the common cases); never build DOM with innerHTML from user data (use textContent); sanitise rich text server-side with an allow-list library (DOMPurify, bleach); a Content-Security-Policy with nonces or hashes and no unsafe-inline as defence in depth; and HttpOnly cookies so a successful XSS still cannot read the session token.

React escapes by default — the hole is dangerouslySetInnerHTML, and href={userInput} accepting javascript: URLs.

03

Explain CSRF and its modern defences.

The attacker's page causes the victim's browser to send a state-changing request to your site; cookies are attached automatically, so it is authenticated. Defences:

  1. Anti-CSRF token — a per-session secret in a form field or header that an attacker's origin cannot read. The synchroniser-token pattern (server-side state) or the double-submit cookie pattern (stateless, must be signed to be sound).
  2. SameSite cookiesLax (the modern browser default) blocks cookies on cross-site POSTs; Strict blocks them on cross-site navigation too. Strong, but not a complete substitute: it does not protect against same-site subdomain attacks and older clients.
  3. Custom header requirement — a non-simple header forces a CORS preflight that the attacker cannot satisfy. This is why bearer-token APIs are largely immune.
  4. Origin/Referer checking for sensitive operations.

The rule: cookie-authenticated state changes need CSRF protection; Authorization: Bearer APIs do not (but must not also accept cookies, or you have reintroduced the problem).

04

How do you store passwords?

A slow, salted, memory-hard hash: Argon2id (preferred), scrypt or bcrypt. Never MD5/SHA-family (they are designed to be fast, which is exactly wrong), never encryption (reversible), never home-rolled schemes.

python
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=3, memory_cost=64*1024, parallelism=4)
hashed = ph.hash(password)              # salt is generated and embedded automatically
try:
    ph.verify(hashed, attempt)
    if ph.check_needs_rehash(hashed):   # upgrade parameters over time
        store(ph.hash(attempt))
except VerifyMismatchError:
    ...

Around it: a per-user random salt (defeats rainbow tables and reveals nothing about shared passwords), an optional server-side pepper in a separate secret store, rate limiting and lockout on login, constant-time comparison everywhere, a check against known-breached password lists (Have I Been Pwned's k-anonymity API), and no maximum length or composition rules beyond a minimum length — NIST guidance is length over character-class theatre.

05

Sessions vs JWTs — which do you choose, and why?

Server-side sessions: an opaque id in an HttpOnly; Secure; SameSite=Lax cookie, with state in Redis/the database. Revocation is instant, the token carries no data, and size is trivial. Cost: a store lookup per request (cheap) and stickiness considerations.

JWTs: signed, self-contained claims. Good for stateless service-to-service calls, short-lived access tokens, and cross-domain federation. Costs, which are the real answer to this question:

  • You cannot revoke them. A stolen token is valid until it expires. Mitigate with short access-token TTLs (5-15 min) plus refresh tokens that are stored and revocable — which means you have server-side state after all.
  • Storing them in localStorage exposes them to XSS. Cookies are safer, but then you need CSRF protection.
  • Implementation footguns: always pin the algorithm server-side (the alg: none and RS256→HS256 confusion attacks), verify iss, aud, exp and nbf, and never put anything sensitive in a payload that is merely base64, not encrypted.

The defensible position for a normal web app: sessions. Reach for JWTs when you have a genuine multi-service or cross-domain requirement.

06

Explain OAuth 2.0 and OIDC at a level you could implement.

OAuth 2.0 is delegated authorisation ("let this app read my calendar"); OIDC is a thin identity layer on top that adds an id_token (a JWT about the user) and a userinfo endpoint — that is what "Sign in with Google" actually uses.

The flow to know is Authorization Code with PKCE, now recommended for all client types:

  1. App redirects to the provider with client_id, redirect_uri, scope, state (CSRF protection), and code_challenge (SHA-256 of a random code_verifier).
  2. User authenticates and consents; provider redirects back with a one-time code.
  3. App exchanges code + code_verifier at the token endpoint for an access token (+ refresh token, + id_token).

PKCE prevents an attacker who intercepts the code from redeeming it, because they lack the verifier. The implicit flow is deprecated — tokens in URL fragments end up in history and logs. Also validate: state matches, the redirect_uri is registered exactly (open redirects are how these get broken), and the id_token signature, iss, aud and nonce.

07

What is the difference between authentication and authorisation, and how do you model permissions?

AuthN = who you are; AuthZ = what you may do. 401 vs 403.

Models: RBAC (users → roles → permissions) is the sensible default and covers most products. ABAC evaluates attributes (department, ownership, time, resource state) when rules are conditional. ReBAC (Google Zanzibar, OpenFGA) models relationships — "editors of a folder can edit documents in it" — and is what you want for nested sharing.

The engineering points that matter more than the acronym: enforce authorisation server-side on every request, at the data layer where possible (a query scoped by tenant cannot leak another tenant's rows); never trust a client-supplied id without an ownership check — that is IDOR, still one of the most common real-world breaches; deny by default; and log authorisation failures, because a spike is an attack signal.

08

What is in the OWASP Top 10, and which do you see most in review?

Current categories: Broken Access Control · Cryptographic Failures · Injection · Insecure Design · Security Misconfiguration · Vulnerable and Outdated Components · Identification and Authentication Failures · Software and Data Integrity Failures · Security Logging and Monitoring Failures · SSRF.

In practice the ones I actually find in code review: broken access control (a missing ownership check on GET /orders/{id}), misconfiguration (debug mode, permissive CORS with credentials, a public storage bucket, default credentials), outdated dependencies with known CVEs, and secrets in the repository. Naming what you personally catch, rather than reciting the list, is what distinguishes the answer.

09

What is SSRF and how do you prevent it?

Server-Side Request Forgery: you fetch a URL supplied by the user, and the attacker points it at internal infrastructure — http://169.254.169.254/ for cloud instance credentials, internal admin panels, or file://.

Defences: an allow-list of permitted hosts (deny-lists lose to DNS rebinding, redirects, decimal/octal IP encodings, and IPv6 forms); resolve the hostname and reject private/link-local ranges and re-check after redirects; disable redirect following or validate each hop; block non-HTTP schemes; and put outbound fetches through an egress proxy in a network segment with no access to metadata endpoints. On AWS, IMDSv2 exists specifically to mitigate this.

10

How do you handle secrets and dependency risk?

Secrets: never in the repository or the image; inject via environment or a secret manager (Vault, AWS Secrets Manager, Doppler) at runtime; rotate on a schedule and immediately on staff departure; scan history with gitleaks/trufflehog in CI, and treat any leaked secret as compromised — rotate, do not just delete the commit. Encrypt at rest and in transit, and keep encryption keys in a KMS so the application never holds raw key material.

Dependencies: pin versions with a lockfile, run pip-audit/npm audit/Dependabot in CI, check what a new dependency actually pulls in before adding it, prefer well-maintained packages, and generate an SBOM for anything you ship. Supply-chain attacks (typosquatting, a compromised maintainer account, malicious post-install scripts) are now a routine threat, so npm ci --ignore-scripts in CI and a private registry mirror are reasonable controls to mention.