Skip to content
Chapter 51Lesson 4

Quiz - The auth mental model

Quiz progress

0 / 0

A reviewer flags this layout, which renders a settings page only when the user’s role is admin:

export default async function SettingsLayout({ children }) {
const user = await getUser();
if (user?.role !== 'admin') return <NotAllowed />;
return <>{children}</>;
}

Why is this the wrong place for that check?

It’s an authorization check placed at a render boundary, not the action boundary. A layout can be skipped under partial pre-rendering, and hiding UI stops nobody who POSTs to the action directly — the gate has to live on the server-side mutation.

The check itself is correct, but it should compare against 'member' instead of 'admin' — layouts are the right home for role gates, the role is just wrong.

A layout can only run authentication checks, so it should return <NotAllowed /> whenever user is null and let the page component handle the role.

A signed-in user requests invoice inv_42, which genuinely exists but belongs to a different organization. The clean rule says authn-fail is 401 and authz-fail is 403. What’s the safest status to return here, and why does it bend the rule?

404 Not Found — a 403 would confirm that inv_42 exists but isn’t theirs, leaking one tenant’s data to another. Masking cross-tenant access as “not found” keeps other organizations’ records invisible.

403 Forbidden — the identity is proven and the action is refused, so the textbook authz-fail code is exactly right and there’s no reason to hide anything.

401 Unauthorized — the user shouldn’t see another org’s data, so treating them as unauthenticated and bouncing them to sign-in is the safe default.

Why does forcing a fresh password prompt before a user changes their billing details count as authentication triggered by an authorization policy, rather than just one or the other?

The rule “this capability needs recent proof” is an authorization policy; the re-prompt it fires — checking a password or passkey again — is authentication. The two concerns hand off to each other rather than being the same thing.

It’s pure authorization: re-prompting is just a stricter permission check, and no new authentication happens because the user is already signed in.

It’s pure authentication: the session already expired, so the user is simply being asked to sign in again from scratch.

A teammate argues for switching the browser session to a JWT because “stateless is modern and skips a database read per request.” For a typical Next.js SaaS, what’s the single property that makes this the wrong default?

Revocation. “Sign out everywhere,” killing a compromised account, and stopping a stolen cookie are each one DELETE with server-stored sessions, but impossible with a pure JWT until it expires — and adding a denylist to fix that re-introduces the per-request read the JWT was chosen to avoid.

Payload size. A JWT carries all its claims in the cookie, so it exceeds the browser’s cookie size limit once you add device and org metadata, which forces a switch back to sessions.

Confidentiality. A JWT’s payload is encrypted, so the server can’t read the user ID without the signing secret, making per-request identity reads slower than an indexed lookup.

A hand-rolled auth path stores user data in the cookie and trusts it at request time. Pick both of the choices below that are genuine bugs in that approach.

Comparing the presented session token to the stored one with === leaks length-of-match through timing; a constant-time compare that examines every byte is required.

Putting the user’s role in the cookie and trusting it at the action boundary lets a stale cookie act with capabilities the server already revoked; the boundary must re-read role from the database.

Generating the session token with crypto.randomUUID() is too weak; only a 64-byte token from crypto.getRandomValues() clears the entropy bar.

Reusing the same session row across a user’s laptop and phone is required so “sign out everywhere” can delete a single row.

Naming the auth cookie __Host-session rather than session does what, exactly?

The browser refuses to store the cookie unless Secure and Path=/ are set and no Domain= is present — turning “is this cookie scoped tightly?” from something the developer must remember into something the platform enforces.

It tells the server to keep the session record in memory on the host rather than in the database, which is what makes the per-request lookup fast.

It encrypts the cookie value with a host-bound key so that document.cookie returns ciphertext instead of the raw token.

In a “Sign in with Google” flow, which sentence captures the relationship between OAuth and OpenID Connect (OIDC)?

OAuth is an authorization protocol that hands the app tokens (“this app may act on the user’s behalf”); OIDC is a layer on top, opted into with the openid scope, that adds the id_token so the app learns who the user is.

OIDC is the authorization protocol and OAuth is the authentication layer built on top of it; you request the oauth scope to get the user’s identity back.

They’re two names for the same protocol; “OIDC” is just what the flow is called once a client_secret is involved.

A new hire on a confidential, server-side app with a client_secret proposes skipping PKCE, reasoning “the secret already proves it’s us.” In OAuth 2.1 (draft-15, 2026), what’s the correct response?

PKCE is mandatory for every client in 2.1, secret or not. A static secret can’t bind one specific code to one specific flow, so it doesn’t stop a stolen code from being replayed — the verifier-and-challenge pair does.

They’re right — PKCE was only ever for public clients (SPAs and mobile) that can’t hold a secret, so a confidential server app can safely turn it off.

PKCE can be skipped as long as the client_secret is rotated per environment, since a unique secret per deployment already binds the flow.

During the authorization-code-with-PKCE flow, which value travels on the front channel (through the browser), and which stays off it?

The code_challenge (the SHA-256 hash of the verifier) and the one-time code ride the front channel; the code_verifier and client_secret travel only on the back-channel token exchange.

The code_verifier and client_secret ride the front channel so the provider can validate them in the redirect; the code is returned only on the back channel.

Everything travels the front channel — the back channel only exists for the final userinfo call after the user is already signed in.

After verifying the id_token and reading the user’s email, what should the app do with the access_token and refresh_token for a pure login flow — and what is the app’s actual session?

The provider’s tokens were just the proof-of-identity input; for a pure login the app can discard them, then mint its own __Host- session cookie. The OAuth tokens are never the app’s session.

The app should store the access_token in the session cookie and present it on every request — it is the session, which is why OAuth removes the need for a separate cookie.

The app should keep the refresh_token in the browser so it can silently re-run the OAuth flow on each navigation and avoid issuing its own session.

Quiz complete

Score by topic