Skip to content
Authentication & Authorization

Cross-cutting Quality

Listen 0%
Speed

26 ยท Authentication & Authorization

Who you are and what youโ€™re allowed to do โ€” from first principles: authentication vs authorization, sessions vs tokens, OAuth 2.0 and OIDC explained from zero (roles, the Authorization Code + PKCE flow, grant types, the three token types), where auth is enforced, RBAC/ABAC/ReBAC, passkeys/WebAuthn, and the 2026 library landscape. The token-storage and threat angle lives in 17; this file is the protocol and model foundation.


Positioning

Auth is the topic every senior frontend engineer is expected to explain end to end, and the one most often half-understood. It spans two distinct questions that get blurred constantly โ€” who are you? (authentication) and what may you do? (authorization) โ€” plus a protocol (OAuth/OIDC) whose vocabulary trips people up. You rarely build an identity provider from scratch (you shouldnโ€™t), but you must understand the flows to integrate a provider correctly, store tokens safely (17), decide sessions-vs-tokens, and put authorization checks in the right place. Getting any of this wrong is how accounts get taken over.


Foundations: the two questions

  • Authentication (authn) โ€” โ€œwho are you?โ€ Proving identity: a password, a magic link, a passkey, a Google login. Output: a verified identity (a user).
  • Authorization (authz) โ€” โ€œwhat are you allowed to do?โ€ Deciding whether this identity may perform this action on this resource: roles, permissions, ownership, scopes. Output: allow/deny.

They are independent and sequential: authenticate first, then authorize each action. Confusing them causes real bugs โ€” โ€œlogged inโ€ (authenticated) is not โ€œallowedโ€ (authorized). A user can be authenticated and still forbidden from editing another userโ€™s order. OAuth 2.0 is an authorization framework; OIDC adds authentication on top โ€” keeping that straight is half the battle (more below).

Two more primitives:

  • Identity & credentials โ€” the user and the secret/factor that proves them (password, passkey, OTP).
  • Session โ€” the server-recognized state of โ€œthis request belongs to an authenticated user,โ€ carried across stateless HTTP requests by a cookie or a token.

Deep dive

1. The fundamental choice: sessions vs tokens

HTTP is stateless, so after login you need to carry identity on each request. Two models:

  • Cookie-based sessions (stateful) โ€” on login the server creates a session record (in memory/Redis/DB) and sends the browser an opaque session ID in an HttpOnly; Secure; SameSite cookie. Each request auto-sends the cookie; the server looks up the session. Pros: simple, instantly revocable (delete the record), token never exposed to JS. Cons: needs a shared session store to scale horizontally (24); requires CSRF defense (SameSite/anti-CSRF, 17). The default for first-party web apps.
  • Token-based (stateless) โ€” the server issues a signed token (usually a JWT) that carries the identity/claims; the client sends it (often Authorization: Bearer โ€ฆ) and the server verifies the signature without a lookup. Pros: stateless, scales trivially, works across services/mobile/edge. Cons: hard to revoke before expiry; storing it in the browser is an XSS liability (17).

2026 consensus for browsers: prefer cookie sessions or the BFF/token-handler pattern (below); avoid a pure SPA holding long-lived JWTs in JS-readable storage.

2. OAuth 2.0 from zero โ€” delegated authorization

OAuth solves one problem: let an app access resources on a userโ€™s behalf without the user handing over their password. โ€œLet this app read your Google Calendarโ€ โ€” without giving the app your Google password. It defines four roles:

  • Resource Owner โ€” the user who owns the data.
  • Client โ€” the app that wants access (your frontend/BFF).
  • Authorization Server (AS) โ€” issues tokens after authenticating the user and getting consent (Google, Auth0, your IdP).
  • Resource Server โ€” the API holding the protected data, which accepts access tokens.

The client never sees the password; it receives a token scoped to specific permissions. That indirection is the whole point.

3. The Authorization Code flow + PKCE (the one youโ€™ll use)

The correct flow for browser and mobile apps. Step by step:

  1. The client generates a random code_verifier and its SHA-256 hash, the code_challenge (this is PKCE).
  2. The client redirects the user to the ASโ€™s /authorize with client_id, redirect_uri, response_type=code, scope, a random state (CSRF protection for the flow), and the code_challenge.
  3. The user authenticates at the AS and consents to the requested scopes. (The client never touches the credentials.)
  4. The AS redirects back to redirect_uri with a short-lived authorization code and the original state.
  5. The client checks state, then exchanges the code at the ASโ€™s /token endpoint, sending the code + the original code_verifier.
  6. The AS verifies the verifier hashes to the earlier challenge, then returns an access token (+ refresh token, + ID token if OIDC).
  7. The client calls the Resource Server with Authorization: Bearer <access token>.

Why PKCE? It stops an authorization-code interception attack: even if a malicious app grabs the redirectโ€™s code, it canโ€™t exchange it without the code_verifier it never saw. PKCE protects token acquisition, not storage (17). OAuth 2.1 makes PKCE mandatory for all clients and removes the old insecure flows.

4. Grant types (flows) โ€” pick by client

  • Authorization Code + PKCE โ€” interactive user login in browser/mobile apps. The default.
  • Client Credentials โ€” machine-to-machine (a backend calling an API as itself, no user).
  • Device Authorization โ€” input-constrained devices (TVs, CLIs): user authorizes on a phone.
  • Refresh Token โ€” exchange a refresh token for a new access token without re-login.
  • Deprecated: Implicit (tokens in the URL fragment โ€” insecure) and Resource Owner Password Credentials (app handles the password โ€” defeats the point). Donโ€™t use these; OAuth 2.1 drops them.

5. The three tokens (donโ€™t conflate them)

  • Access token โ€” grants access to an API/resource; short-lived; sent as Bearer. Carries scopes (coarse permissions: read:orders) and an audience (which API itโ€™s for). Opaque or JWT. Authorization artifact.
  • ID token (OIDC only) โ€” a JWT asserting who the user is (claims: sub, name, email, nonce). Itโ€™s for the client to learn the userโ€™s identity โ€” not for calling APIs. Authentication artifact.
  • Refresh token โ€” long-lived credential to mint new access tokens; store most carefully (17); rotate it; revocable.

JWT vs opaque tokens: a JWT is self-contained and signed, so the resource server validates it without calling the AS (stateless, fast) โ€” but it canโ€™t be easily revoked before expiry, and its payload is only base64 (readable โ€” never put secrets in it). An opaque token is a random string the resource server must check via the ASโ€™s introspection endpoint (a round trip) โ€” but itโ€™s instantly revocable. Trade statelessness against revocation.

6. OpenID Connect (OIDC) โ€” authentication on top of OAuth

OAuth 2.0 alone only authorizes access; it doesnโ€™t actually tell the client who logged in in a standard way. OIDC is a thin identity layer over OAuth that adds: the openid scope, the ID token, a /userinfo endpoint, and standard claims. โ€œLog in with Google/Appleโ€ and most SSO is OIDC. Mnemonic: OAuth = authorization (what you can do); OIDC = authentication (who you are). A nonce binds the ID token to the request (replay protection), complementing state (CSRF protection).

7. Where tokens live โ€” and the BFF/token-handler pattern

Covered in depth in 17; the short version: access token in memory, refresh token in an HttpOnly cookie, never tokens in localStorage. The most robust browser approach is the Backend-for-Frontend (BFF) / token-handler pattern (12): a server component does the OAuth flow and holds the tokens server-side; the browser only ever gets a secure HttpOnly session cookie; the BFF attaches the real tokens to downstream API calls. Tokens never touch JavaScript, so XSS canโ€™t steal them. In Next.js, route handlers / Server Actions / RSC act as this BFF (08).

8. Authorization models โ€” deciding โ€œwhat may you do?โ€

  • RBAC (Role-Based) โ€” users have roles; roles grant permissions (admin, editor, viewer). Simple, ubiquitous, the right default for most apps.
  • ABAC (Attribute-Based) โ€” policies evaluate attributes of the user, resource, action, and context (โ€œeditors in the same org, during business hoursโ€). More flexible, more complex.
  • ReBAC (Relationship-Based) โ€” permissions derive from relationships in a graph (โ€œuser is editor of doc โ†’ can editโ€); the Google Zanzibar model, via OpenFGA/SpiceDB. For fine-grained, hierarchical, multi-tenant sharing.
  • OAuth scopes are coarse, API-level grants, not a full authz model โ€” donโ€™t mistake โ€œhas the read:orders scopeโ€ for โ€œis allowed to read this orderโ€ (thatโ€™s a resource-level ownership check you still must do).

9. Where authorization is enforced (the part people get wrong)

The client is never the authority. Hiding a button or guarding a route in React is UX, not security โ€” anyone can call the API directly. Every protected action must be authorized on the server, at or near the data: the API/route handler, the Server Action, the resolver. A hard 2026 lesson: Next.js middleware-only auth checks are bypassable (CVE-2025-29927 โ€” a spoofed x-middleware-subrequest header) โ€” so donโ€™t put authorization solely in middleware; enforce it in the handler/data layer (08, 17). Validate the tokenโ€™s signature, expiry, audience, and scopes server-side on every request, then do the resource-level ownership/permission check.

10. Passwordless & passkeys (WebAuthn / FIDO2)

The direction of travel in 2026. A passkey is a public/private key pair created per site; the private key never leaves the device (unlocked by biometrics/PIN), the server stores only the public key. Login is a challenge-response: the server sends a random challenge, the authenticator signs it with the private key, the server verifies with the public key. This is phishing-resistant (nothing shareable to steal, bound to the origin) and kills password reuse, phishing, and credential stuffing. WebAuthn is the W3C browser API; FIDO2/CTAP the device side. Two ceremonies: registration (create + store the public key) and authentication (sign a challenge). Passkeys are now mainstream (the FIDO Alliance reports the vast majority of devices are passkey-ready and billions of accounts support passwordless), and most auth libraries support them out of the box. Pair any scheme with MFA (something you know/have/are); passkeys are inherently strong-factor.

11. SSO, social login & enterprise

  • Social login (โ€œContinue with Google/GitHubโ€) โ€” OIDC against a public IdP; fast onboarding, no password to manage.
  • SSO (Single Sign-On) โ€” one login across many apps, via OIDC or SAML (the older XML enterprise standard still dominant in B2B). Enterprises require it; providers like WorkOS specialize in turning many customersโ€™ SAML/OIDC setups into one integration.

12. The 2026 library/provider landscape (donโ€™t roll your own)

  • Better Auth โ€” modern, self-hosted, TypeScript; batteries included (social, magic links, passkeys, 2FA, organizations, RBAC) via plugins; framework-agnostic. The recommended pick for new self-hosted projects, and it now stewards Auth.js maintenance.
  • Auth.js v5 (formerly NextAuth) โ€” the largest installed base, 70+ OAuth providers, rewritten for the App Router, JWT-at-edge. Now effectively in maintenance mode (security patches; new projects are steered toward Better Auth) โ€” still fine for existing apps.
  • Clerk โ€” hosted identity platform; fastest setup, polished pre-built UI components, built-in passkeys/orgs/device management; per-MAU pricing. Great for shipping B2B SaaS fast.
  • WorkOS โ€” B2B enterprise SSO/SAML as a first-class feature, generous free tier.
  • Supabase Auth โ€” tightly integrated with Supabase Postgres + Row-Level Security.
  • Auth0 / Cognito / Keycloak / Entra ID โ€” established enterprise IdPs.

Build your own only with a strong reason โ€” auth is high-stakes, easy to get subtly wrong, and well served by these.


Worked example: Authorization Code + PKCE, with a BFF holding the tokens

Browser (SPA)            BFF (your server, 12)             Auth Server (IdP)        API
  โ”‚  click "Log in"          โ”‚                                   โ”‚                  โ”‚
  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ make code_verifier + challenge    โ”‚                  โ”‚
  โ”‚   302 redirect to IdP โ—€โ”€โ”€โ”€โ”ค /authorize?โ€ฆ&code_challenge&state โ”‚                  โ”‚
  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ user logs in +   โ”‚
  โ”‚                          โ”‚                                    โ”‚ consents         โ”‚
  โ”‚   302 back with ?code&state โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค                  โ”‚
  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ verify state                      โ”‚                  โ”‚
  โ”‚                          โ”œโ”€ POST /token  code + code_verifier โ–ถโ”‚ verify PKCE     โ”‚
  โ”‚                          โ”‚โ—€โ”€โ”€โ”€โ”€ access + refresh (+ id) token โ”€โ”ค                  โ”‚
  โ”‚  Set-Cookie: session=โ€ฆ   โ”‚  store tokens SERVER-SIDE,                            โ”‚
  โ”‚   HttpOnly;Secure;SameSiteโ”‚  hand the browser only a session cookie              โ”‚
  โ”‚โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค                                                       โ”‚
  โ”‚  GET /api/orders (cookie) โ”‚                                                       โ”‚
  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ look up session โ†’ attach Bearer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ verify
  โ”‚        data โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”คโ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ data โ”€โ”€โ”€โ”€โ”€โ”€โ”ค token+scope+owner

Why itโ€™s robust: the browser never holds a token (XSS canโ€™t steal what isnโ€™t there), PKCE secures the code exchange, state blocks CSRF on the callback, and the API still independently verifies the tokenโ€™s signature/expiry/audience/scope and that the caller owns the resource. Authorization is enforced at the API, not in the React UI.


Pitfalls & gotchas

  • Confusing authn and authz โ€” treating โ€œlogged inโ€ as โ€œallowedโ€; skipping the resource-level ownership check after a scope check.
  • Enforcing authz only on the client (hidden buttons/guarded routes) โ€” itโ€™s UX; the API must enforce. Anyone can curl the endpoint.
  • Middleware-only authorization in Next.js โ€” bypassable (CVE-2025-29927); enforce in the handler/data layer too (08, 17).
  • Tokens in localStorage โ€” XSS-readable; prefer cookies/BFF (17).
  • Using the Implicit or Password grant โ€” deprecated/insecure; use Auth Code + PKCE.
  • Skipping state/nonce โ€” opens CSRF on the callback / ID-token replay.
  • Treating the ID token as an API access token (or vice versa) โ€” different audiences and purposes.
  • JWTs you canโ€™t revoke โ€” long-lived access tokens with no rotation/denylist; keep them short, rotate refresh tokens.
  • Trusting a JWT without verifying signature, exp, aud, iss server-side.
  • Rolling your own crypto/auth โ€” almost always a mistake.
  • Putting secrets in a JWT payload โ€” itโ€™s base64, not encrypted.

Interview questions

  1. Authentication vs authorization โ€” define each and give a bug caused by confusing them.
  2. What problem does OAuth 2.0 actually solve, and what are its four roles?
  3. Walk through the Authorization Code + PKCE flow. What does PKCE protect against?
  4. What do state and nonce defend against?
  5. Access token vs ID token vs refresh token โ€” purpose and audience of each.
  6. OAuth vs OIDC โ€” what does OIDC add, and which is โ€œloginโ€?
  7. JWT vs opaque tokens โ€” trade-offs (statelessness vs revocation)?
  8. Sessions vs tokens โ€” when would you choose each for a web app?
  9. Where must authorization be enforced, and why isnโ€™t the client enough? (Bonus: the Next.js middleware CVE.)
  10. RBAC vs ABAC vs ReBAC โ€” when each? Are OAuth scopes an authz model?
  11. How do passkeys/WebAuthn work, and why are they phishing-resistant?
  12. Describe the BFF/token-handler pattern and why itโ€™s safer for browsers.

Recommendations

  • Keep authn and authz separate in your head and your code; always do a server-side resource-level check, not just a scope/role check.
  • Use Authorization Code + PKCE (OAuth 2.1) for user login; client credentials for machine-to-machine. Never implicit/password grants.
  • For browsers, prefer cookie sessions or the BFF/token-handler pattern (12); access token in memory + refresh token in HttpOnly cookie; never localStorage (17).
  • Enforce authorization at the API/data layer, never only in the UI or middleware (CVE-2025-29927).
  • Verify every JWTโ€™s signature, exp, aud, iss server-side; keep access tokens short; rotate refresh tokens.
  • Default to RBAC; reach for ABAC/ReBAC (OpenFGA/SpiceDB) only when fine-grained sharing demands it.
  • Adopt passkeys/WebAuthn and MFA; treat passwords as legacy.
  • Use a vetted provider/library (Better Auth, Clerk, WorkOS, Auth.js v5, Supabase Auth, Auth0) rather than building auth yourself.

Books & references

  • โ€œOAuth 2.0 Simplifiedโ€ โ€” Aaron Parecki (oauth.net/2, free). The clearest from-zero walkthrough of roles, flows, and PKCE; start here.
  • โ€œOAuth 2 in Actionโ€ โ€” Justin Richer & Antonio Sanso (Manning). The thorough book-length treatment.
  • The specs: OAuth 2.0 (RFC 6749) + the OAuth 2.1 draft, PKCE (RFC 7636), OAuth 2.0 for Browser-Based Apps (BCP โ€” the authoritative SPA guidance), and OpenID Connect Core (openid.net).
  • OWASP โ€” Authentication, Session Management, and JWT Cheat Sheets; the threat-side companion to this file (17).
  • WebAuthn (w3.org/TR/webauthn) and passkeys.dev / FIDO Alliance โ€” the passwordless standards and guides.
  • auth0.com/docs and Okta/Auth0 blog โ€” vendor-neutral-ish explainers of flows, tokens, and OIDC (read past the product pitch).
  • Provider/library docs: Better Auth, Auth.js (authjs.dev), Clerk, WorkOS, Supabase Auth โ€” current, practical integration guides.

Connections

Frontend Deep-Dive Library ยท content is the single source of truth.