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; SameSitecookie. 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:
- The client generates a random
code_verifierand its SHA-256 hash, thecode_challenge(this is PKCE). - The client redirects the user to the ASโs
/authorizewithclient_id,redirect_uri,response_type=code,scope, a randomstate(CSRF protection for the flow), and thecode_challenge. - The user authenticates at the AS and consents to the requested scopes. (The client never touches the credentials.)
- The AS redirects back to
redirect_uriwith a short-lived authorizationcodeand the originalstate. - The client checks
state, then exchanges the code at the ASโs/tokenendpoint, sending thecode+ the originalcode_verifier. - The AS verifies the verifier hashes to the earlier challenge, then returns an access token (+ refresh token, + ID token if OIDC).
- 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:ordersscopeโ 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,issserver-side. - Rolling your own crypto/auth โ almost always a mistake.
- Putting secrets in a JWT payload โ itโs base64, not encrypted.
Interview questions
- Authentication vs authorization โ define each and give a bug caused by confusing them.
- What problem does OAuth 2.0 actually solve, and what are its four roles?
- Walk through the Authorization Code + PKCE flow. What does PKCE protect against?
- What do
stateandnoncedefend against? - Access token vs ID token vs refresh token โ purpose and audience of each.
- OAuth vs OIDC โ what does OIDC add, and which is โloginโ?
- JWT vs opaque tokens โ trade-offs (statelessness vs revocation)?
- Sessions vs tokens โ when would you choose each for a web app?
- Where must authorization be enforced, and why isnโt the client enough? (Bonus: the Next.js middleware CVE.)
- RBAC vs ABAC vs ReBAC โ when each? Are OAuth scopes an authz model?
- How do passkeys/WebAuthn work, and why are they phishing-resistant?
- 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 inHttpOnlycookie; neverlocalStorage(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,issserver-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
17-security.mdโ token storage, XSS/CSRF defenses, CSP, and the RSC/Server-Actions threat surface; the security half of auth.12-bff-and-data-enrichment.mdโ the BFF/token-handler pattern; where the OAuth flow and tokens live server-side.08-nextjs-and-meta-frameworks.mdโ route handlers/Server Actions/middleware as the enforcement point; the middleware CVE.13-microservices-and-orchestration.md/24-system-and-infrastructure-architecture.mdโ API gateways, service-to-service auth (client credentials), session stores, SSO at scale.04-the-web-platform.mdโ cookies,fetchcredentials, the WebAuthn API.25-architecture-decisions-and-tradeoffs.mdโ sessions-vs-tokens and self-hosted-vs-managed-auth as architecture decisions.