401 vs 403, JWT, OAuth, PKCE: Security and Sign-In Words Explained (Spring + Entra ID)
▶ Watch on YouTube & subscribe to The Stack Underflow
An online bookstore. A customer signs in with Microsoft, the Spring Boot app remembers them and calls an orders API for them, and every night a job with no user calls that API too. The two deep-dives after this video trace all of that as state machines, and they use a lot of words that sound alike: filter chain, 401, CSRF, JWT, OAuth, PKCE. This page explains them first.
The one-line version: authentication asks who are you?, authorization asks may you do this?, and every token, cookie and status code below exists to carry or check one of those two answers.
Last verified against the Spring Security 7.1.1 reference, the Microsoft identity platform docs and the OAuth, JWT, PKCE, HTTP and OpenID Connect specifications: 1 October 2026. Targets, as in the video: Spring Security 7.1.1 on Spring Boot 4.1.1, and Microsoft Entra ID (v2.0 endpoints).
One picture: the building’s security desk
Every word gets a peg from one analogy: an office building’s security desk. You pass the security checkpoints, show your ID at the desk, and get a visitor badge that decides which doors open. Sometimes head office’s security team checks you instead and hands out key cards. The pegs are memory aids, not facts; the tables say where each one stops matching.
Who are you, and what may you do?
| Word | Memory peg | What it actually is |
|---|---|---|
| Authentication | checking your ID at the desk | ”how we verify the identity of who is trying to access a particular resource” (Spring Security). In the bookstore the ID is checked at head office (Entra), not by the app. |
| Authorization | which doors your badge opens | ”determining who is allowed to access a particular resource”. Checked on every request, not once at a front door. |
| Principal | the name on the badge | The part of Spring’s Authentication object that identifies the user. |
| Credentials | the ID you show | The proof, often a password; in many cases cleared after authentication. With Entra sign-in the password is typed on Microsoft’s page and never reaches the app. |
| Password hash | keep a fingerprint, not a copy | Apps that keep their own passwords store a one-way hash. Spring’s default DelegatingPasswordEncoder encodes with BCrypt (prefix {bcrypt}), which the docs call “deliberately slow”. Stolen hashes still allow offline guessing; slowness makes each guess costly. |
Remembering you between requests
| Word | Memory peg | What it actually is |
|---|---|---|
| Cookie | the numbered slip you carry | Small data a server sends; the browser sends it back on later requests. A stolen slip works for anyone, so send it only over HTTPS. |
| Session | the visitor list at the desk | Data the server keeps for one visitor; the browser holds only its ID, in a cookie usually named JSESSIONID. |
SecurityContext | the visitor badge you wear | Holds the current Authentication for this request, in a ThreadLocal by default, cleared by FilterChainProxy when the request ends. After sign-in, the bookstore app saves it in the session and loads it again on the next request. |
| Stateful vs stateless | the visitor list vs a signed badge you carry | A session can be ended at the server at once. A token the API checks by itself is normally accepted until it expires, so keep it short-lived; but any number of servers can check it without shared memory. |
The checkpoints
| Word | Memory peg | What it actually is |
|---|---|---|
| Filter chain | the security checkpoints | Servlet filters that run before your controller in a fixed order (CORS, CSRF, login, authorization and more). An app can have several SecurityFilterChains; only the first that matches runs. A checkpoint can also act, like sending you off to sign in. |
| Authority (role, scope) | the clearances on your badge | Granted permissions. hasRole('ADMIN') is “a shortcut for hasAuthority that prefixes ROLE_”, so it checks ROLE_ADMIN. A JWT scope like Orders.Read becomes SCOPE_Orders.Read. So hasRole never matches a scope; use hasAuthority. |
| 401 | who are you? Go to the desk | RFC 9110: “the request lacks valid authentication credentials for the target resource.” A browser on a chain with a login page gets a redirect to sign in instead. |
| 403 | your badge doesn’t open this door | RFC 9110: “the server understood the request but refuses to fulfill it.” |
| CSRF token | the desk’s stamp on the form | Another site makes your browser post a form to the bookstore; if your session cookie comes along, it looks like you. Chrome usually holds that cookie back on cross-site posts; not every browser does. The fix is a random token your pages include and the other site can’t read. Spring checks it on unsafe methods like POST; missing or wrong gets 403. Bearer-token API calls skip it. |
| CORS | a note for the courier | The browser’s same-origin policy stops a script from one origin reading another’s responses. CORS lets a server tell the browser which other origins’ scripts may read its responses, sometimes after a preflight. curl ignores the note: CORS doesn’t protect your API, and it isn’t CSRF protection. |
Who decides 401 or 403? Spring’s ExceptionTranslationFilter. If access is denied while you are anonymous, it starts authentication through the AuthenticationEntryPoint (a 401, or a redirect to the login page). If you are signed in, it calls the AccessDeniedHandler: 403.
Head office and its tokens
| Word | Memory peg | What it actually is |
|---|---|---|
| JWT | a signed badge you carry | RFC 7519: “a compact, URL-safe means of representing claims”. Claims include issuer (iss), subject (sub), audience (aud) and expiry (exp). A signed JWT has three parts joined by dots: header, claims, signature. The signature proves origin and integrity, but the parts are only encoded, so the holder can read them. Keep secrets out. |
| Signing keys, JWKS | the published seal samples | The issuer signs with a private key and publishes public keys as a JSON Web Key Set. The API picks one by the kid in the token header. Entra “rotates the possible set of keys on a periodic basis”, so fetch keys, never hard-code them. |
| OAuth 2.0 | the key-card system | RFC 6749: lets an app “obtain limited access to an HTTP service”. Roles: client (the bookstore app, the front desk), resource server (the orders API, the card reader), authorization server (Entra ID), resource owner (the customer). OAuth hands out key cards; it doesn’t tell the app who you are. |
| OpenID Connect | key cards plus introductions | ”a simple identity layer on top of the OAuth 2.0 protocol”. It adds sign-in and the ID token. |
| Identity provider | head office’s security team | The service that signs users in and issues tokens, here Microsoft Entra ID, which Microsoft also calls the authorization server. |
| MFA | two proofs at head office | Two or more of: something you know, something you have, something you are. With Entra, prompts are part of Entra’s sign-in, Conditional Access policies (or security defaults) decide when, and “You don’t need to change apps and services”. The app sees only the signed result. |
One sign-in, three tokens
| Word | Memory peg | What it actually is |
|---|---|---|
| Authorization code | the claim ticket | Sent back through the browser after sign-in. Short-lived (Entra: “Typically, they expire after about 1 minute”) and single-use (RFC 6749: “MUST NOT use the authorization code more than once”). Redeemed at the token endpoint with the app’s own credential. |
| PKCE (pronounced “pixy”) | a secret the desk keeps | The app sends a hash of a random verifier first (S256), then the verifier to redeem, so an intercepted code is useless to anyone else. In Spring Security 7.1.1, requireProofKey defaults to true for the authorization_code grant: on by default. |
| Redirect URI | the only address for the ticket | Entra sends the code only to a registered URI; it “must exactly match one of the redirect URIs you registered”. |
| State and nonce | matching stubs | State comes back with the code and must match what the app saved (stops forged responses, CSRF on the redirect). Nonce comes back inside the ID token (stops replays). They bind a reply to this sign-in; they don’t identify the customer. |
| ID token | a signed letter of introduction | Tells the app who signed in. For the app only: “You shouldn’t use an ID token to call an API.” |
| Access token | the key card for one building | For one API, its audience (aud). Clients “should treat them as opaque strings”; the API validates it. Entra’s default lifetime is a random value between 60 and 90 minutes. |
| Refresh token | the renewal slip | Gets new tokens without a new sign-in. “Only provided if offline_access scope was requested.” |
| Scope, consent | doors named on the card; your signature on the request | Delegated permissions the app asks for to act for the user. Entra asks the user to consent unless an admin consented for the organization. Granted scopes appear in the scp claim; Spring turns them into SCOPE_ authorities. |
| Client credentials | the desk’s own key card | The app gets a token as itself, “instead of impersonating a user”, asking for {resource}/.default. Permissions are app roles an admin grants. “Refresh tokens will never be granted with this flow.” Guard the credential; Microsoft recommends certificates over secrets. |
| On-behalf-of | swap cards for the next building | An API that must call another API as the same user exchanges its token at Entra (jwt-bearer grant, requested_token_use=on_behalf_of). It never forwards it. |
| SAML 2.0 | the XML letter of introduction | ”A mature, XML-based standard used widely in enterprises.” Entra posts a signed XML assertion to the app, not OAuth tokens. For new apps, “OIDC is usually simpler to build with modern frameworks.” |
Pause & Prove
The pinned question
A signed-in customer opens a page their account isn’t allowed to see. 401 or 403? And what happens to an anonymous visitor who is denied on a chain with a login page?
- Signed in and refused: 403. The server knows who they are and refuses: your badge doesn’t open this door.
- Anonymous and denied: “we don’t know who you are.”
ExceptionTranslationFilterstarts authentication. On a chain with a login page, the browser is redirected to sign in; without one, the entry point answers 401.
The Community poll: in Spring Security, hasRole('ADMIN') checks which authority?
ADMIN. The tempting answer. That is whathasAuthority('ADMIN')would check.ROLE_ADMIN. ✓hasRoleadds theROLE_prefix.SCOPE_ADMIN. That is how a JWT scope namedADMINwould be mapped.hasRolenever matches a scope; usehasAuthority('SCOPE_…')for scopes.- Any of them. No: the check is one exact authority string.
The peg drill
Authentication: checking your ID at the desk. SecurityContext: the visitor badge you wear. JWT: a signed badge you carry. Authorization code: the claim ticket. Access token: the key card for one building.
Before and after this video
- Next: One request through Spring Security: the filter chain, 401 vs 403, from Tomcat to your controller: the filter chain, CSRF, JWTs, 401 and 403.
- Then: Signing in to Spring Boot with Microsoft Entra ID, the whole sign-in: the claim ticket, PKCE, three tokens, sign-out, and the job with no user.
- Earlier in this series: Backend words: port, socket, reverse proxy, JVM, pod, SIGTERM.
Sources
All pages were read on 1 October 2026; Spring pages show “Spring Security 7.1.1”.
- Spring Security: servlet architecture: first matching
SecurityFilterChainonly;ExceptionTranslationFilter(entry point vsAccessDeniedHandler) - Spring Security: authentication architecture: principal, credentials, authorities;
ThreadLocal; context cleared after each request - Spring Security: password storage:
DelegatingPasswordEncoderwith{bcrypt}; “deliberately slow” - Spring Security: authorize HTTP requests:
hasRoleprefixesROLE_ - Spring Security: CSRF (servlet): unsafe methods by default; missing or invalid token →
AccessDeniedHandler, 403 - Spring Security: OAuth 2.0 resource server JWT:
scope/scpmapped with theSCOPE_prefix - Spring Security: OAuth 2.0 client core and
ClientRegistration.javaat 7.1.1:requireProofKeydefaults totrueforauthorization_code - RFC 9110 §15.5.2, §15.5.4: 401 and 403 definitions
- RFC 6749: OAuth 2.0, the four roles, single-use code
- RFC 7519: JWT definition and claims; encrypted JWTs for privacy
- RFC 7636: PKCE, “pixy”, S256
- OpenID Connect Core 1.0: identity layer, ID token, nonce against replay, state against CSRF
- Microsoft: access tokens: opaque to clients, 60–90 minute default, three JWT segments,
kid, key rotation - Microsoft: auth code flow: code about 1 minute, exact redirect URI,
code_challengerecommended for all clients,offline_access, certificate credentials - Microsoft: client credentials flow: own credentials,
.default, admin-granted app permissions, no refresh tokens - Microsoft: on-behalf-of flow: token exchange; don’t forward middle-tier tokens
- Microsoft: MFA overview: three factor kinds; no app changes; Conditional Access
- Microsoft: what is single sign-on: SAML 2.0 and OIDC descriptions
Change notes
- 1 Oct 2026: first published.
Not affiliated with or endorsed by Microsoft or VMware Broadcom (Spring). Found a mistake? Tell us in the video’s comments and we’ll correct this page.
Found this useful? The deep version lives on YouTube — new breakdowns of how AI dev tools actually work, weekly.
Subscribe on YouTube →Prefer email? Get the free newsletter: one failure, traced step by step, about once a week.