Sign In with Microsoft Entra ID in Spring Boot: OIDC, OAuth 2.0, PKCE, Client Credentials, SAML

October 1, 2026 · How It Actually Works: Real Systems as State Machines (part 12)

▶ Watch on YouTube & subscribe to The Stack Underflow

You open a protected page, sign in with Microsoft, and land back, signed in. In between, the browser, your Spring Boot app and Microsoft Entra ID trade redirects, a one-time code and three kinds of token. This page follows every step, then the API that receives the token, and a background job with no user at all.

The one-line version: OAuth 2.0 grants access; OpenID Connect adds sign-in. The ID token is for your app; an access token is for one API.

Last verified against Microsoft Learn (Microsoft identity platform) and the Spring Security 7.1.1 reference: 1 October 2026. Scope: a server-side Spring Boot 4.1 web app (Spring Security 7.1.1), Entra ID v2.0 endpoints, one tenant, plain Spring Security properties (what the Spring Cloud Azure starter sets up for you). Single-page, mobile and multi-tenant apps and External ID are out of scope.

The words, as memory pegs

The video reuses the office building from the Spring Security video. The pegs are analogies to help you remember, not definitions:

WordMemory peg (analogy)What it actually is
Identity provider (Entra ID)Head office’s security teamThe service that authenticates the user and issues tokens
Authorization codeThe claim ticketA short-lived, one-time code returned through the browser
PKCEA secret the desk keepsA random verifier; only its hash travels in the first redirect
ID tokenA signed letter of introductionA JWT saying who signed in, addressed to your app
Access tokenThe key card for one buildingA token for one API, its audience
Refresh tokenThe renewal slipGets new access tokens without asking the user
Redirect URIThe only address for the ticketMust exactly match one registered on the app
State and nonceMatching stubsState comes back with the code; nonce is sealed inside the ID token

Entra ID is Azure AD’s new name since 2023; Microsoft says the login URLs, APIs and MSAL stay the same.

Setup: register with head office, point Spring Boot at it

  1. Register the app in Entra: you get a client ID and tenant ID, and you add a Web redirect URI. Spring’s default template is {baseUrl}/login/oauth2/code/{registrationId}, so a registration named entra uses /login/oauth2/code/entra.
  2. Give it a credential. Microsoft recommends a certificate or federated credential for production. A client secret is limited to 24 months at most (Microsoft recommends under 12), and Microsoft says secrets should not be used in production.
  3. Permissions. Delegated permissions (scopes) let the app act for a signed-in user and appear in the scp claim. App roles appear in the roles claim, for assigned users or admin-approved apps.
  4. Configure Boot with the registration and the issuer:
spring:
  security:
    oauth2:
      client:
        registration:
          entra:
            client-id: <client-id>
            client-secret: <secret>
            scope: openid, profile, email, offline_access
        provider:
          entra:
            issuer-uri: https://login.microsoftonline.com/<tenant-id>/v2.0

At startup, Spring reads the discovery document at that issuer: the authorize, token, UserInfo and logout endpoints, and the JWKS URI with Entra’s public signing keys. If the document can’t be fetched, or its issuer doesn’t exactly match the configured one, the app does not start.

The front channel: through the browser

A visitor asks for a protected page with no badge, so Spring Security saves the request and redirects the browser to Entra with:

  • response_type=code, client_id, redirect_uri, scope
  • a random state and nonce
  • a PKCE code_challenge: the hash (S256) of a secret verifier Spring keeps

PKCE is on by default since Spring Security 7: in 7.1.1, ClientRegistration’s client settings default to requireProofKey = true, and the reference says you must set it to false only if the provider doesn’t support PKCE for confidential clients. Microsoft recommends PKCE “for all application types, both public and confidential clients”.

Entra then authenticates the user, with MFA and Conditional Access as needed. To the app that’s a black box; it never sees the password. On first use, Entra asks the user to consent to the requested permissions, unless an admin already consented for everyone. Cancel or decline, and the browser comes back with error=access_denied.

On success, Entra redirects to the redirect URI with the code and the state. The code expires after about a minute and is useless without the PKCE verifier. Everything here travels as browser redirects, so no secret and no token goes this way; only the short-lived code does.

The back channel: redeeming the claim ticket

The callback reaches Spring Security’s login filter, and now the app talks to Entra directly:

  1. State first. The returned state must match the one saved in the session. It proves this browser started the request, which stops cross-site request forgery. A missing or different state matches no saved request, so Spring rejects it (authorization_request_not_found) before any token is requested.
  2. Token request. The app posts the code, the PKCE verifier and its credential to the token endpoint.
  3. Three tokens back: an ID token, an access token for Microsoft Graph (sign-in requested only OIDC scopes), and, because offline_access was requested, a refresh token.

A used or expired code, or a wrong verifier, gets invalid_grant. A wrong or expired credential gets invalid_client.

The ID token: check the letter, then issue the badge

Spring Security validates the ID token before trusting it:

CheckWhat must hold
SignatureVerifies with the public key named by the token’s kid, from Entra’s JWKS endpoint. Keys rotate, so never hard-code one
issEquals the configured issuer
audContains this app’s client ID
expNot expired
nonceMatches the nonce saved at the start (stops an old ID token being replayed)

Any failure is an invalid ID token. Spring then builds an OidcUser from the ID token plus UserInfo from Microsoft Graph, fetched with that access token. Authorities: OIDC_USER and one SCOPE_ authority per scope. Entra’s roles claim needs a GrantedAuthoritiesMapper. The user goes into the SecurityContext (the badge), saved in the HTTP session, and the browser returns to the page it first asked for.

Never call an API with the ID token. Microsoft’s guidance: “You shouldn’t use an ID token to call an API.”

Calling an API: one key card per building

The controller calls the orders API with a RestClient and Spring’s OAuth2 interceptor, which gets a token from the authorized client manager:

  • Each access token is for one API, its audience. The Graph token from sign-in won’t open the orders API. With no token for that API yet, Spring sends the browser back to Entra for the API’s scope (for example api://orders/Orders.Read), the same code flow with PKCE. If the Entra session is alive and consent was given, there is no prompt.
  • Lifetime. Entra access tokens get a random default lifetime between 60 and 90 minutes (75 on average).
  • Refresh. An expired token is renewed with the refresh token, without asking the user; Entra returns a new refresh token that replaces the old one. A revoked or expired refresh token gets invalid_grant; Spring drops the stored token, so the next call goes back to Entra.
  • Opaque to the client. The token travels as Authorization: Bearer .... The client never reads it; only the API validates it.

Sign-out: at the app and at head office

Sign-out has two layers. Spring Security invalidates the HTTP session and clears the SecurityContext. With OidcClientInitiatedLogoutSuccessHandler, the browser then goes to Entra’s end-session endpoint with the ID token as a hint and a registered post-logout redirect URI. Entra ends the user’s session for this app and can notify other apps through their front-channel logout URLs.

With local logout only, the Entra session survives, and the next sign-in may need no password.

The API’s side: checking the key card

The orders API is a Spring Boot resource server; its filter chain is the Spring Security video. What’s specific to Entra:

  • Token version. Set the API registration to issue v2 tokens (requestedAccessTokenVersion: 2); the default (null) gives v1 tokens, with a different issuer, which would fail a v2.0 issuer check.
  • Audience. In a v2 token, aud is the API’s client ID. Set the audiences property: accepting another API’s token is the confused deputy problem.
  • Scopes and roles. Spring maps scp to SCOPE_ authorities by default; Entra’s roles claim needs its own converter, for example to ROLE_ authorities.
  • Answers. An invalid token gets 401 with a WWW-Authenticate header; a valid token without the needed scope or role gets 403.
  • Calling another API as the user? Don’t forward the token; exchange it at Entra with the on-behalf-of flow.

No user: client credentials

A scheduled job signs in as itself: no browser, no consent screen, no ID token, no refresh token. It sends its client ID, its credential and the scope {API ID URI}/.default, which means the app permissions an admin granted for that API. The token carries roles, not scp. Spring’s authorized client manager reuses the cached app token until a minute before it expires. A wrong or expired credential gets invalid_client: an expired secret is a classic outage.

Which protocol when

SituationUseYou get
Sign a user inOpenID Connect (auth code + PKCE)ID token (+ access token, + refresh token with offline_access)
Call an API for that userOAuth 2.0 access token (delegated scopes)One access token per API (scp)
A service with no userClient credentialsApp-only access token (roles), no refresh token
An API calling another API as the userOn-behalf-ofA new access token for the downstream API
An enterprise app that only speaks SAMLSAML 2.0A signed SAML assertion, no access token for APIs

For SAML, Entra posts a signed assertion to the app; Spring Security’s SAML 2.0 login processes it at POST /login/saml2/sso/{registrationId}.

Pause & Prove

1. After sign-in, your app holds an ID token and an access token. It needs to call your orders API. Can it send either?

Neither. The ID token is for your app, never for calling an API. The access token from sign-in is for Microsoft Graph, and each access token is for one API, so it won’t open the orders API. Spring gets a separate access token for the orders API’s scope, and the API checks that aud is its own client ID.

2. A nightly job uses the client credentials flow with Entra ID. What does it get back?

  • An ID token and an access token. No user signed in, so there is no ID token.
  • An access token and a refresh token. Tempting, but Microsoft says refresh tokens are never granted with this flow; the job simply asks again.
  • Only an access token. ✓ An app-only access token, carrying roles, not scp.
  • Only a refresh token. There’s nothing to refresh; the job needs an access token to call the API.

Peg drill

Identity provider (head office’s security team) · authorization code (the claim ticket) · PKCE (a secret the desk keeps) · ID token (a signed letter of introduction) · access token (the key card for one building) · refresh token (the renewal slip) · state and nonce (matching stubs).

Before / after this video

Sources

Checked on 1 October 2026:

Change notes

  • 1 Oct 2026: first published.

Not affiliated with or endorsed by Microsoft, VMware Broadcom or the Spring team. Microsoft Entra is a trademark of the Microsoft group of companies; Spring is a trademark of Broadcom Inc. and/or its subsidiaries. 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.