ExplainerAug 28, 20263 min read

401 or 403? Ask one question

“Do I know who you are?” If no, 401. If yes but you can’t do this, 403. Everything else is detail.

The names are misleading: 401 Unauthorized is really about authentication, and 403 Forbidden is about authorization.

401ErrorUnauthorizedThe request has no valid credentials: the token is missing, malformed or expired. Send Authorization: Bearer <token>, and refresh the token on 401 before retrying once.

A 401 must carry a WWW-Authenticate header telling the client how to authenticate. Retrying with a fresh token can fix it. A 403 will fail the same way no matter how many times you log in.

route.ts
if (!session) return new Response(null, { status: 401, headers: { "WWW-Authenticate": "Bearer" } });
if (!can(session.user, "invoice:read", invoice)) return new Response(null, { status: 403 });
403ErrorForbiddenThe server knows who you are but refuses the action: missing role, scope, IP allowlist or WAF block. Re-authenticating will not help; grant the permission or change the request.
Tip

Hiding a resource’s existence? Return 404 instead of 403. GitHub does this for private repositories.