Skip to content
Backend & systems

36 Backend Authentication and Security Interview Questions and Answers

This focused guide turns RecallDeck’s curated Backend Authentication and Security material into 36 interview-ready questions. Answer each one before opening the explanation, then use the examples and edge cases to repair anything vague or incomplete.

28 min read36 detailed answersReviewed Aug 24, 2026
What to remember

Organize the answer around ownership, limits, failure, and recovery. Definitions become interview-ready when they survive a concrete production scenario.

Question set

36 detailed answers

01

What's the difference between authentication and authorization?

Short answer: Authentication — "who are you?" (verifying identity). Authorization — "what are you allowed to do?" (checking permissions). Authentication first, then authorization.

In depth:

  • Authentication (AuthN) — establishing identity. The user proves they are who they claim to be: login+password, token, fingerprint, certificate. Result — the system knows who this is.
  • Authorization (AuthZ) — checking that the authenticated subject has the right to perform an action or access a resource. Result — "allowed" or "denied" (often 403 Forbidden).

Example of HTTP statuses:

  • 401 Unauthorized — actually means "not authenticated" (invalid/missing token).
  • 403 Forbidden — authenticated, but not authorized (lacks permissions).
Request → [AuthN: who are you?] → [AuthZ: are you allowed this?] → resource
            ↓ no                    ↓ no
           401                     403

⚠️ Gotcha: The HTTP status names are misleading: 401 Unauthorized is about authentication, not authorization. Authorization is 403 Forbidden.

02

How does session-based authentication work?

Short answer: After login, the server creates a session, stores its state on its side, and gives the client a session id in a cookie. On every request the browser sends the cookie, and the server looks up the session by the id.

In depth:

  1. The user sends login/password.
  2. The server verifies them, creates a session record (e.g., {userId: 42, role: admin}) in a store and generates a random, unpredictable session id.
  3. The server sets a cookie: Set-Cookie: sid=abc123; HttpOnly; Secure; SameSite=Lax.
  4. The browser automatically attaches the cookie to all subsequent requests to the same domain.
  5. The server uses the sid to fetch the session and figure out who this is.

A session is stateful: the state lives on the server, the client only has a pointer (the id).

Set-Cookie: sid=8f3a...; HttpOnly; Secure; SameSite=Strict; Max-Age=3600; Path=/

⚠️ Gotcha: The session id must be cryptographically random and long enough. If it can be predicted or brute-forced — that's session hijacking. Never put predictable data into the id (e.g., an incrementing userId).

03

Where and how are sessions stored on the server?

Short answer: In a server-side store: in memory, in Redis/Memcached, or in a DB. In distributed systems — centrally (Redis), otherwise a session is "pinned" to one instance.

In depth:

  • In-memory (process memory) — fast, but lost on restart and doesn't work with multiple instances without sticky sessions.
  • Redis / Memcached — the production standard: fast, supports TTL (auto-expiry), shared across all instances.
  • DB — reliable, but slower.

Under horizontal scaling, sessions must be in a shared store, otherwise a user who lands on a different instance via the load balancer ends up "logged out." An alternative is sticky sessions (the load balancer always sends the user to the same server), but that's fragile.

⚠️ Gotcha: Storing sessions in an instance's memory is the main cause of "random logouts" after a deploy/scaling. This is a common argument in favor of JWT (stateless), but JWT has its own downsides (see below).

04

What is a JWT and how is it structured?

Short answer: A JWT (JSON Web Token) is a self-contained token made of three parts header.payload.signature, encoded in Base64URL and separated by dots. The signature guarantees integrity.

In depth:

eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiI0MiIsInJvbGUiOiJhZG1pbiJ9 . SflKxwRJ...
   header (Base64URL)        payload (Base64URL)                signature
  • Header — the algorithm and type: {"alg":"HS256","typ":"JWT"}.
  • Payload — claims. Standard ones: sub (subject/userId), iss (issuer), aud (audience), exp (expiry), iat (issued at), nbf (not before). Plus custom ones: role, email.
  • Signature — a signature over base64(header) + "." + base64(payload) with a secret key.

A JWT is stateless: all the information is inside the token, the server doesn't need to store state — it only verifies the signature.

Pros: stateless, easy to scale, convenient for microservices and inter-service authentication, no round-trip to a session store needed. Cons: can't be easily revoked, larger than a cookie with a session id, the data inside is visible to everyone (only signed, not encrypted).

⚠️ Gotcha: The payload is not encrypted, only Base64URL-encoded and signed. Anyone can read the contents. Never put passwords, card tokens, or PII into a JWT — it's publicly readable. Base64 ≠ encryption.

05

HS256 vs RS256 — what's the difference?

Short answer: HS256 — symmetric (one shared secret for signing and verifying, HMAC-SHA256). RS256 — asymmetric (the private key signs, the public key verifies, RSA-SHA256).

In depth:

  • HS256 (HMAC) — there's one secret. Whoever can verify can also sign (forge). Suitable when the same service both issues and verifies tokens.
  • RS256 (RSA) — only the auth server signs with the private key, and any service can verify with the public key without being able to forge. Ideal for microservices and third-party consumers.
HS256:  sign(secret)  →  verify(secret)        // one key
RS256:  sign(privKey) →  verify(pubKey)         // a key pair

⚠️ Gotcha: The classic "alg: none" and "alg confusion" attacks: an attacker changes the header to alg:none (no signature) or to HS256, using the public RSA key as the HMAC secret. Defense: on the server, hard-code the expected algorithm, don't trust the alg field from the token.

06

How does the server validate a JWT?

Short answer: It recomputes the signature over header+payload with its own key and compares it to the token's signature; it checks exp, iss, aud, nbf. If something doesn't match — the token is invalid.

In depth:

  1. Split the token into three parts.
  2. Recompute the signature over header.payload with the key and compare it to the third part.
  3. Check exp (not expired), nbf/iat (time), iss (an issuer we trust), aud (intended for us).
  4. Only after a successful check, trust the claims (role, sub).

Validation is purely cryptographic and doesn't require hitting the DB — that's the strength of stateless.

⚠️ Gotcha: Never trust the payload without verifying the signature. And don't parse the token "by hand" without checking exp/alg — use a vetted library. The signature comparison should be constant-time to rule out timing attacks.

07

Why is a JWT hard to revoke, and what can you do about it?

Short answer: A JWT is stateless — the server doesn't store issued tokens, so until exp the token is always valid, even if the user logged out or got banned. Solutions: short TTL + refresh, blacklist, token versions.

In depth: With sessions it's enough to delete the record from the store — and access is closed instantly. With a JWT there's no such button. Approaches:

  • Short TTL (5–15 min) on the access token + a refresh token. Then the compromise "window" is small.
  • Blacklist / denylist — store revoked jti's in Redis with a TTL up to their exp. This brings back a stateful component (a trade-off).
  • Token versioning — store a tokenVersion on the user; increment it on logout/password change. The version is put in the token and checked against the DB on verification (also a round-trip).

⚠️ Gotcha: "JWT = you can log out by deleting it on the client" is a myth. Deleting the token in the browser doesn't make it invalid on the server; if a copy leaked, it works until exp. Real logout requires server-side denylist logic or short TTLs.

09

Why do we need refresh tokens, and what is token rotation?

Short answer: The access token is short-lived (minutes) for requests; the refresh token is long-lived and used only to obtain new access tokens. Rotation — each refresh issues a new refresh token and invalidates the old one.

In depth:

  • Access token — short TTL, sent on every request; if it leaks, it expires quickly.
  • Refresh token — long TTL, stored as securely as possible (HttpOnly cookie / secure storage), sent only to the refresh endpoint.
  • Token rotation — on every use of the refresh token, the server issues a new pair and marks the old refresh as used. If someone tries to reuse the old refresh — reuse detection: it means the token was stolen, and the server revokes the entire chain (logs out all sessions).
[access 10m] expired → POST /refresh (refresh token) → new access + new refresh
                                                        old refresh → invalidated

⚠️ Gotcha: Without rotation and reuse detection, a stolen refresh token gives an attacker endless access. Refresh tokens need to be stored on the server (stateful) to allow revocation — that's a "return" to state, but only for refresh, not for every request.

10

Sessions vs JWT — when to choose which?

Short answer: Sessions — for classic web apps with a single backend, when you need instant revocation. JWT — for stateless APIs, microservices, inter-service authentication, and scaling without a shared store.

In depth:

Criterion Session (stateful) JWT (stateless)
State storage On the server (Redis/DB) In the token on the client
Revocation Instant (delete the record) Hard (blacklist/TTL)
Scaling Needs a shared store Easy, nothing to store
Round-trip to the store On every request Not needed (just verify the signature)
Size Small (id in a cookie) Larger (the whole payload)
Microservices Inconvenient Convenient
Leak Kill the session — done Valid until exp

💡 In practice it's often a hybrid: a JWT access token (short) + a server-side refresh with the ability to revoke.

⚠️ Gotcha: People pick JWT "because it's stateless and trendy," then add a blacklist for revocation — and lose the main advantage (statelessness). If you need instant revocation and the app is a monolith — sessions are simpler and safer.

11

OAuth 2.0 — the roles and why it's needed?

Short answer: OAuth 2.0 is a protocol for delegated authorization: it lets an application get limited access to a user's resources on another service without knowing their password. It's about access, not login.

In depth — four roles:

  • Resource Owner — the user, the owner of the data.
  • Client — the application that wants access (e.g., a third-party service).
  • Authorization Server — issues tokens (e.g., accounts.google.com).
  • Resource Server — the API where the data lives and which verifies the access token (e.g., the Google Drive API).

Example: an app wants to read your photos in Google. Instead of entering your Google password into a third-party app, you log in at Google and consent to the scope photos.read. The app gets an access token with that scope.

⚠️ Gotcha: OAuth 2.0 is authorization (access to resources), not authentication (who logged in). Using "raw" OAuth2 for login ("Login with X") is wrong — for that there's OpenID Connect (see below). A common mistake: treating the access token as proof of identity.

12

OAuth 2.0 — what grant types are there?

Short answer: The main one today is Authorization Code + PKCE. For server-to-server — Client Credentials. Implicit and Resource Owner Password Credentials are deprecated and not recommended.

In depth:

  • Authorization Code + PKCE — the standard for web, SPAs, and mobile. The client gets a short code via a redirect, then exchanges it for a token on the backend. PKCE (code_verifier/code_challenge) protects against code interception for public clients.
  • Client Credentials — machine-to-machine, no user. The service authenticates with its own credentials and gets a token.
  • Implicit (deprecated) — the token goes straight into the redirect/URL; vulnerable (token in the browser history, logs). Replaced by Auth Code + PKCE.
  • Resource Owner Password Credentials (ROPC) (deprecated) — the app collects the user's login/password directly; contradicts the essence of OAuth, must not be used.
Auth Code + PKCE:
client → /authorize?code_challenge=... → login+consent → redirect ?code=XYZ
client → /token (code=XYZ, code_verifier=...) → access_token (+refresh, +id_token)

⚠️ Gotcha: If in an interview someone proposes Implicit or the Password grant for a new application — that's a red flag. Today, for all clients (including SPAs), Authorization Code + PKCE is recommended.

13

Access token vs refresh token in OAuth?

Short answer: The access token — for hitting the Resource Server, short-lived. The refresh token — for getting new access tokens without logging in again, long-lived, sent only to the Authorization Server.

In depth: The access token is presented in Authorization: Bearer <token> to the resource server. When it expires, the client uses the refresh token at the auth server's token endpoint and gets a new access token. The refresh is never sent to the resource server.

⚠️ Gotcha: Don't confuse scope (what the token is allowed to do) with identity. An access token can be "opaque" — then the resource server validates it via an introspection endpoint rather than parsing it itself.

14

OpenID Connect (OIDC) vs OAuth2?

Short answer: OIDC is an authentication layer on top of OAuth 2.0. OAuth2 answers "what's allowed" (access authorization), OIDC adds "who logged in" (authentication) via the id_token.

In depth:

  • OAuth2 gives an access_token for accessing an API.
  • OIDC additionally issues an id_token — a JWT with information about the user (claims: sub, email, name, iss, aud, exp). It's the one that confirms identity.
  • OIDC adds a standardized /userinfo endpoint and discovery (.well-known/openid-configuration).

"Login with Google/Apple/GitHub" = OIDC, not pure OAuth2.

⚠️ Gotcha: The access_token can't be used to authenticate the user — it's opaque to the client and intended for the resource server. For "who is this" you need the id_token. Confusing access/id tokens is a common SSO implementation mistake.

15

What are SSO and SAML?

Short answer: SSO (Single Sign-On) — one login to access many applications. SAML — an XML protocol for exchanging authentication data (assertions) between an Identity Provider and a Service Provider, popular in enterprise.

In depth:

  • SSO — the user logs in once at an Identity Provider (IdP), then enters different applications (SPs) without re-entering a password. Implemented via SAML, OIDC, Kerberos.
  • SAML 2.0 — old but alive in the corporate world (Okta, AD FS). The IdP sends a signed XML assertion with the user's identity to the SP. Heavyweight (XML, signatures), but widely supported.
  • OIDC — a modern alternative to SAML on JSON/JWT, lighter for mobile and SPAs.

⚠️ Gotcha: SAML assertions are signed with an XML signature; XML Signature Wrapping vulnerabilities allowed forging an assertion. You must strictly validate the signature of the whole document and trust only correctly signed elements.

16

How to store passwords correctly?

Short answer: Never in plaintext and not with a regular hash. Use specialized slow algorithms: argon2 (preferred), bcrypt, or scrypt — with a salt built into the algorithm.

In depth:

  • Passwords are stored as a hash, non-reversible.
  • Regular hashes (MD5, SHA-1, SHA-256) are too fast — a GPU computes billions per second, brute force and rainbow tables are effective.
  • Password algorithms are deliberately slow and tunable by a cost factor:
    • argon2id — the winner of the Password Hashing Competition, resistant to GPU and memory-hard attacks. Recommended today.
    • bcrypt — proven, with a work factor (cost). Password length limit ~72 bytes.
    • scrypt — memory-hard, also good.
hash = bcrypt(password, cost=12)   // the salt is generated and stored inside the hash string

⚠️ Gotcha: "I added a salt to SHA-256, that's enough" — no. A salt kills rainbow tables, but doesn't save you from brute force with a fast hash. You need specifically a slow algorithm. And don't write your own crypto — use libraries.

17

Salt, pepper, and why a slow hash is a good thing?

Short answer: Salt — a unique random string per password, stored alongside the hash, protects against rainbow tables and identical hashes for identical passwords. Pepper — a shared secret kept separately from the DB. A slow hash makes brute force economically unviable.

In depth:

  • Salt — added to the password before hashing. Unique per user ⇒ two identical passwords give different hashes, and precomputed tables are useless. The salt is not a secret, it usually sits in the same string (bcrypt/argon2 include it automatically).
  • Pepper — an additional secret not stored in the DB (in env/vault/HSM). If the DB leaks without the pepper, brute force is impossible without the secret. An extra layer.
  • Slow hash — even if the DB is stolen, brute-forcing each password takes noticeable time; with a cost factor you can tune the "expense" to keep up with hardware growth.

⚠️ Gotcha: The salt must not be globally the same for everyone ("static salt") — that's essentially a pepper, not a salt, and doesn't protect against identical passwords producing identical hashes. The salt is per user.

18

Hashing vs Encryption vs Encoding?

Short answer: Encoding — a reversible transformation without a secret (Base64), not security. Encryption — reversible with a key. Hashing — non-reversible (one-way). They're constantly confused.

In depth:

Reversible? Needs a key/secret? Purpose
Encoding (Base64, URL-encode) Yes, by anyone No Representing data, not protection
Encryption (AES, RSA) Yes, with a key Yes Confidentiality
Hashing (SHA-256, bcrypt) No No (sometimes salt/pepper) Integrity, password verification

Passwords — are hashed (non-reversibly). Data you need to read back (a card number for a payment) — is encrypted. Base64 is just an encoding, it doesn't "protect" anyone.

⚠️ Gotcha: "I encoded the password in Base64" — that's not protection, anyone decodes it instantly. A JWT payload is also only Base64URL — readable by everyone. Encoding ≠ encryption ≠ hashing.

19

Symmetric vs asymmetric encryption?

Short answer: Symmetric — one key for encryption and decryption (AES), fast. Asymmetric — a key pair: the public key encrypts/verifies, the private key decrypts/signs (RSA, ECC), slower, solves the key-exchange problem.

In depth:

  • Symmetric (AES) — fast, for large volumes of data. Problem: how to securely deliver the shared key to both parties.
  • Asymmetric (RSA, ECC) — the public key can be handed out to everyone; what it encrypts can only be decrypted by the private key. Used for key exchange and digital signatures.
  • In practice it's a hybrid: TLS uses asymmetric crypto to negotiate a shared session key, then symmetric (AES) for the data itself — the best of both worlds.

⚠️ Gotcha: Asymmetric encryption isn't used for large data directly — it's slow and limited by the key size. It's used to encrypt only the symmetric key itself.

20

Why do we need HTTPS/TLS?

Short answer: TLS provides confidentiality (traffic encryption), integrity (it can't be altered unnoticed), and server authenticity (a certificate). It protects against interception and MITM attacks.

In depth: Without HTTPS, traffic goes in plaintext — your ISP, public Wi-Fi, anyone on the path can read passwords, cookies, data and substitute content. TLS:

  1. Encrypts — intercepted data can't be read.
  2. Integrity — packets can't be altered unnoticed.
  3. Authenticity — a certificate signed by a CA confirms you're talking to the real server, not a man-in-the-middle.

MITM (Man-in-the-Middle) — an attacker sits between the client and the server, reading/changing traffic. TLS + a valid certificate prevents this from being done unnoticed (the client will see a certificate error).

⚠️ Gotcha: HTTPS protects data in transit, but not from XSS, SQLi, a DB leak, or bad authorization. "We have HTTPS, so we're secure" is a misconception. Also, the cookie Secure flag is mandatory, otherwise the cookie will go over HTTP too.

21

What is the OWASP Top 10?

Short answer: A list of the 10 most critical web security risks from OWASP, updated periodically. It's a baseline checklist of what to verify.

In depth (OWASP Top 10, 2021 edition, the key ones):

  1. Broken Access Control — the most common: IDOR, bypassing permission checks, privilege escalation.
  2. Cryptographic Failures — weak/missing encryption, plaintext passwords, data over HTTP.
  3. Injection — SQLi, command injection, LDAP injection (XSS is also classified here).
  4. Insecure Design — flaws at the architecture level, lack of threat modeling.
  5. Security Misconfiguration — default passwords, exposed admin panels, unnecessary headers/features, incorrect CORS.
  6. Vulnerable and Outdated Components — outdated dependencies with known CVEs.
  7. Identification and Authentication Failures — weak passwords, no MFA, poor session management.
  8. Software and Data Integrity Failures — insecure updates, deserialization, CI/CD supply chain.
  9. Security Logging and Monitoring Failures — no logs/alerts for attacks.
  10. Server-Side Request Forgery (SSRF) — the server is tricked into reaching internal resources.

⚠️ Gotcha: Broken Access Control is #1, yet it's the one most often underestimated by relying on "the frontend won't show the button." The server must check authorization on every request.

22

SQL injection — how does it work and how do you defend against it?

Short answer: An attack where user input ends up in a SQL query as code rather than as data. The defense is parameterized queries (prepared statements), an ORM, and input validation.

In depth: Vulnerable code concatenates input:

# VULNERABLE
query = "SELECT * FROM users WHERE name = '" + name + "'"
# input: name = "' OR '1'='1"  → returns all users
# input: name = "'; DROP TABLE users;--"  → drops the table

The defense is parameterized queries: values are passed separately from the query text, and the DBMS treats them strictly as data:

# SAFE
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
  • ORMs (SQLAlchemy, Hibernate, Prisma) parameterize by default.
  • Principle of least privilege for the application's DB user.
  • Validation/allowlist for dynamic parts (column names can't be parameterized — only allowlisted).

⚠️ Gotcha: An ORM is no silver bullet — raw() queries, dynamic ORDER BY/table names, and LIKE with concatenation are still vulnerable. Only values can be parameterized, not identifiers — for those you need a strict allowlist.

23

XSS — types and defenses?

Short answer: Cross-Site Scripting — injecting someone else's JS into a page, executed in the victim's browser. Types: stored, reflected, DOM-based. Defenses: output escaping, CSP, HttpOnly cookies.

In depth:

  • Stored XSS — the malicious script is saved on the server (a comment, a profile) and served to all visitors. The most dangerous.
  • Reflected XSS — a script in a request parameter is reflected back in the response (e.g., a search page), triggered via a bait link.
  • DOM-based XSS — a vulnerability in client-side JS that unsafely inserts data into the DOM (innerHTML, document.write); the server isn't involved.

What XSS can do: steal cookies/tokens, make requests on the victim's behalf, keylog, deface.

Defenses:

  • Output escaping according to context (HTML, attribute, JS, URL). Modern frameworks (React, Angular) escape by default.
  • CSP (Content-Security-Policy) — restricts script sources, blocks inline scripts.
  • HttpOnly cookies — JS can't read the token (reduces theft).
  • Avoid innerHTML/dangerouslySetInnerHTML/eval with user data; sanitize HTML (DOMPurify).

⚠️ Gotcha: Escaping must be done per output context, not "on input." The same string is safe in HTML text but dangerous inside <script> or an href="javascript:..." attribute. Blind input filtering corrupts data and doesn't cover all contexts.

24

CSRF — how does the attack work and how do you defend against it?

Short answer: Cross-Site Request Forgery — it makes the victim's browser perform an unwanted request to a site where they're authenticated, using automatically attached cookies. Defenses: a CSRF token, SameSite cookies, checking Origin/Referer.

In depth: The victim is logged into their bank (session cookie). They visit the attacker's site, which has a hidden form/image:

<img src="https://bank.com/transfer?to=attacker&amount=1000">

The browser automatically attaches the bank's cookie to this request — the transfer goes out on the victim's behalf. The attacker doesn't see the response, but the action is performed.

Defenses:

  • CSRF token (synchronizer token) — the server issues an unpredictable token in the form/header; a foreign site doesn't know it.
  • SameSite=Lax/Strict on cookies — the browser doesn't send cookies on cross-site requests. The main modern defense.
  • Checking Origin/Referer headers on the server.
  • Use safe methods: state-changing operations only via POST/PUT/DELETE, not GET.

⚠️ Gotcha: CSRF is only relevant when authentication is automatic (cookies). If the token is in an Authorization header (which the browser won't attach by itself), classic CSRF doesn't work — but then you have the XSS risk of storing it. SameSite=Lax doesn't fully protect against certain GET navigations.

25

CORS and the same-origin policy?

Short answer: The Same-Origin Policy forbids JS from reading responses from another origin. CORS is a mechanism by which the server allows cross-domain requests via Access-Control-* headers. CORS relaxes SOP; it does not protect the server.

In depth:

  • Origin = scheme + host + port. https://a.com and https://b.com (or a different port/scheme) are different origins.
  • Same-Origin Policy (SOP) — a browser rule: a script from one origin cannot read a response from another origin.
  • CORS — the server uses headers to tell the browser who is allowed:
    • Access-Control-Allow-Origin: https://app.com
    • Access-Control-Allow-Methods: GET, POST
    • Access-Control-Allow-Headers: Authorization, Content-Type
    • Access-Control-Allow-Credentials: true (allow cookies)
  • Preflight — for "non-simple" requests (methods other than GET/POST/HEAD, custom headers) the browser first sends an OPTIONS request to ask permission, and only then the actual request.
Browser → OPTIONS /api (preflight) → Access-Control-Allow-Origin: https://app.com
Browser → POST /api (if allowed)

⚠️ Gotcha: CORS is NOT server protection. It governs what the browser allows JS to read. The request still reaches the server (curl/Postman ignore CORS). A dangerous misconfiguration: Access-Control-Allow-Origin: * together with Allow-Credentials: true (the spec forbids this), or reflecting Origin without checking an allowlist — this opens user data to any site. CORS does not replace authentication/authorization.

26

Authorization: RBAC vs ABAC, scopes, least privilege?

Short answer: RBAC — permissions via roles (admin, editor). ABAC — permissions via attributes (department, time, owner). Scopes — granular token permissions. Least privilege — grant the minimum necessary.

In depth:

  • RBAC (Role-Based) — users are assigned roles, roles are assigned permissions. Simple, but coarse for complex rules.
  • ABAC (Attribute-Based) — decisions based on attributes of the subject, resource, and context: "a manager can view a document if they are the author OR are from the same department AND it's business hours." Flexible, harder to maintain.
  • Scopes — in OAuth/tokens: what the token is allowed to do (read:orders, write:profile).
  • Principle of Least Privilege — each component/user gets only the access strictly needed. Fewer permissions means less damage on compromise.

⚠️ Gotcha: Authorization must be checked on the server, on every request, and at the object level (does this user have a right to THIS record?), not just "the role has access to the endpoint." Otherwise you get IDOR. Hiding a button on the frontend is not authorization.

27

Rate limiting and brute-force protection?

Short answer: Limiting the number of requests per interval (per IP/user/key). It protects against password guessing, DoS, and scraping. It's complemented by account lockout, CAPTCHA, and exponential backoff.

In depth:

  • Rate limiting — algorithms like token bucket and sliding window. A 429 Too Many Requests response when exceeded.
  • Brute-force protection for login:
    • Progressive delay (exponential backoff) after failures.
    • Account lockout — temporary block after N failures.
    • CAPTCHA after several errors.
    • Monitoring for credential stuffing (attempts using known leaked passwords).
  • It's better to apply limits both at the IP level and at the account level (so a distributed attack from many IPs doesn't bypass per-IP limits).

⚠️ Gotcha: A hard account lockout itself becomes a DoS vector: an attacker deliberately locks out other people's accounts by guessing passwords. Often a progressive delay + MFA + monitoring is better than a blunt lockout. And don't reveal whether a login exists ("invalid login or password" — a single, uniform message).

28

Secrets management — how do you store secrets?

Short answer: Never store secrets (keys, DB passwords, tokens) in code/the repository. Use environment variables, or better — secret managers (Vault, AWS Secrets Manager, KMS) with rotation and auditing.

In depth:

  • Not in code/Git — a secret in the repository = leaked forever (history, forks). If one gets in — rotate it immediately, don't just delete the commit.
  • Env vars / .env — the baseline level; .env in .gitignore. Better than hardcoding, but not ideal (visible in the process environment, dumps).
  • Secret managers / Vault — centralized storage, encryption at rest, access control, auditing, rotation, dynamic short-lived credentials.
  • KMS/HSM — for encryption keys.
  • Secret scanners in CI (git-secrets, trufflehog, gitleaks) — catch leaks before commit.

⚠️ Gotcha: Deleting a secret from the latest commit isn't enough — it remains in the Git history and with everyone who cloned the repo. The only correct fix is to rotate (revoke) the compromised secret.

29

Security headers (CSP, HSTS, X-Frame-Options)?

Short answer: HTTP headers by which the server instructs the browser to harden security: CSP (resource sources), HSTS (HTTPS only), X-Frame-Options (anti-clickjacking), X-Content-Type-Options (anti-MIME-sniffing).

In depth:

  • Content-Security-Policy — an allowlist of sources for scripts/styles/images; the main defense against XSS and injection. Example: default-src 'self'; script-src 'self'.
  • Strict-Transport-Security (HSTS) — the browser always uses HTTPS for the domain, even if you typed http. max-age=31536000; includeSubDomains. Protects against SSL stripping.
  • X-Frame-Options: DENY/SAMEORIGIN — forbids embedding the page in an <iframe> ⇒ protects against clickjacking. (The modern equivalent is frame-ancestors in CSP.)
  • X-Content-Type-Options: nosniff — the browser doesn't "guess" the MIME type, it uses the declared one ⇒ protects against executing disguised files.
  • Additionally: Referrer-Policy, Permissions-Policy, the absence of Server/X-Powered-By (information hygiene).

⚠️ Gotcha: CSP is easy to render useless: script-src 'unsafe-inline' 'unsafe-eval' or * nullify the protection. CSP must be rolled out gradually (report-only mode), otherwise you'll break legitimate scripts.

30

IDOR — Insecure Direct Object Reference?

Short answer: A vulnerability where the application grants access to an object by its identifier without checking that the user has a right to THIS object. A subtype of Broken Access Control.

In depth: The endpoint GET /api/invoices/1043 returns an invoice by id. If the server doesn't check that the invoice belongs to the current user, the attacker simply changes the id to 1044, 1045 and reads other people's data.

GET /api/orders/1001  (mine)      → 200 OK
GET /api/orders/1002  (not mine)  → should be 403, but returns 200 = IDOR

Defenses:

  • Check ownership/permissions at the object level on every request (WHERE id=? AND owner_id=currentUser).
  • Don't rely on the "unpredictability" of ids; a UUID helps against guessing but doesn't replace a permission check.

⚠️ Gotcha: "Our ids are long UUIDs, you can't guess them" — that's security through obscurity. A UUID doesn't replace an authorization check: the link can leak, and access must still be verified.

31

Mass assignment?

Short answer: A vulnerability where the application blindly maps all fields from the request body onto a model object, letting the user set fields they shouldn't (isAdmin, role, balance).

In depth: A profile update form sends {name, email}. But the client adds {"name":"X","email":"y","isAdmin":true}, and if the code does user.update(request.body) without filtering — the user becomes an admin.

// VULNERABLE
user.update(req.body);              // accepts any fields

// SAFE — allowlist of fields (DTO)
const { name, email } = req.body;
user.update({ name, email });

Defense: an allowlist of permitted fields (strong parameters in Rails, DTO/binding models, @JsonIgnore), separating input models from domain ones.

⚠️ Gotcha: Using the same ORM model both for accepting input and as the domain entity is a direct path to mass assignment. Separate input DTOs from entities.

32

2FA, MFA, TOTP?

Short answer: MFA — multiple authentication factors (something you know / have / are). 2FA — a special case (two factors). TOTP — a time-based one-time code from an authenticator app.

In depth:

  • Factors: knowledge (password), possession (phone, key), biometrics (fingerprint).
  • 2FA = password + a second factor. Sharply reduces the risk of compromise when a password leaks.
  • TOTP (Time-based One-Time Password) — an app (Google Authenticator, Authy) and the server share a common secret; the code is computed from the secret + the current time (a ~30-second window). It doesn't require a network.
  • 2FA methods by reliability: hardware keys (WebAuthn/FIDO2/U2F) > a TOTP app > SMS (vulnerable to SIM-swap, interception).

⚠️ Gotcha: SMS as a second factor is weak (SIM-swap, SS7 interception). TOTP or hardware keys are preferable. It's also important to protect the recovery process/recovery codes — otherwise MFA is bypassed via "I lost access."

33

Why can't you store passwords in plaintext, and why use a slow hash?

Short answer: If the DB leaks, plaintext passwords instantly compromise everyone (and other sites too — people reuse passwords). A hash is irreversible. A slow hash makes mass cracking economically impractical.

In depth: Even employees shouldn't see passwords. You store an irreversible hash — on login you hash the entered value and compare. But a fast hash (SHA-256) can be brute-forced billions of times per second on a GPU. A slow one (argon2/bcrypt) with a tunable cost slows each attempt by thousands of times ⇒ cracking a stolen database becomes impractical. A salt eliminates rainbow tables and identical hashes.

⚠️ Gotcha: "I'll encrypt passwords with AES" — bad: encryption is reversible, and if the key leaks, all passwords are exposed. Passwords are specifically hashed, not encrypted.

35

Why is XSS more dangerous than CSRF?

Short answer: XSS executes the attacker's arbitrary code in the victim's browser — it can do anything the user can: read tokens, bypass CSRF tokens, keylog. CSRF only forces a specific, pre-guessed action to be performed blindly.

In depth: With CSRF, the attacker doesn't see the response and is limited to known-in-advance requests; it's defended with SameSite/a CSRF token. With XSS, code runs in the site's context: it reads the DOM, responses, local tokens, reads and inserts the CSRF token from the page, makes any authenticated requests. That is, XSS bypasses CSRF protection. That's why XSS is fundamentally more dangerous.

⚠️ Gotcha: CSRF protection (CSRF tokens) doesn't help against XSS — a script on the page will just read that token itself. You close XSS first.

36

What does "don't trust the client" mean?

Short answer: Any data and checks on the client (browser, mobile app) can be forged. All security checks — validation, authorization, limits — must be duplicated and be authoritative on the server.

In depth: The client is fully under the user's control: they can modify the JS, send a request via curl/Postman, tamper with headers, prices, ids, hidden fields, bypass form validation. Therefore:

  • Frontend validation is for UX only; server-side validation is mandatory.
  • Authorization — on the server, on every request and every object (not "hid the button").
  • Prices, discounts, permissions, quantities — recompute/verify on the server, don't trust values from the request (see mass assignment).

⚠️ Gotcha: A hidden field, a disabled button, a frontend-only check, a "secret" endpoint without authorization — all of these are trivially bypassed. The server is the only trust boundary.

Source notes

References and review policy

RecallDeck’s interview answers are editorial material, reviewed against maintained official documentation where a primary reference is available. Tool selections use direct provider links and contain no affiliate placements. Features can change after the review date.

From reading to recall

Practice the full interview loop.

RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.

Start studying

Keep going

RecallDeck Interview Library

Detailed answers from the same curated interview deck, organized for search, study, and durable recall.

RSS