What Passes Between
Microsoft Entra ID: Integrations & Access Troubleshooting
Course 1 ยท Chapter 3 ยท What Passes Between Entra and Your System
Chapter 2 told you what an integration is. This chapter follows what actually travels when it works: what your system sends to Entra, what Entra sends back, and what your system must check before it believes the answer. Once you know the steps, a failure stops being "the integration is broken" and becomes "step four failed, and step four is where the credential is used".
Pattern 1: An OpenID Connect Sign-in, Step by Step
This is the usual "Sign in with Microsoft" flow. Microsoft calls it the OAuth 2.0 authorization code flow combined with OpenID Connect. Here it is as one picture, then in detail:
Step 1: your system sends the browser to Entra
Your system redirects the user to the tenant's /authorize endpoint, with details in the address. For the
authorization code flow, Microsoft documents these parameters:
| Parameter | What it says |
|---|---|
| {tenant} (in the path) | Who may sign in: a tenant ID or domain for one organisation, or organizations / common / consumers |
| client_id | The Application (client) ID of your registration |
| response_type | Must include code |
| redirect_uri | Where Entra should send the user back. It must exactly match one of the redirect URIs registered for the application |
| scope | The permissions requested; for sign-in, openid (plus profile / email if wanted) |
| state | A value your system gets back unchanged, used to guard against forged requests |
| code_challenge (PKCE) | A one-way fingerprint of a secret your system will reveal later; recommended for all application types |
| nonce | Needed when an ID token is requested directly from /authorize; it returns inside the ID token to prevent replay |
Step 2: Entra signs the user in
This happens entirely inside the client's tenant. Entra shows the sign-in page, checks credentials, applies the tenant's
policies (Conditional Access, MFA; Chapter 6), and makes sure the user has consented to the permissions requested in
scope. Some permissions are admin-restricted, and a user who can't consent to them gets an error saying so. Nothing
in your system is involved in this step, and it is where "it works for most users but not one" problems usually live.
Step 3: Entra sends the browser back with a code
On success Entra redirects to your redirect_uri carrying a code (an authorization code) and your
state. Microsoft notes that authorization codes are short-lived and typically expire after about one minute. On
failure it sends back error and error_description instead. Your system should check that the returned
state matches what it sent.
Step 4: your server redeems the code, and proves who it is
This is the one step where your system speaks to Entra directly, as itself. It POSTs to the tenant's
/token endpoint with:
For a web application the credential is required. It can be a client secret, or a certificate credential
(the request then carries a client_assertion, a signed token, instead of client_secret). Microsoft says that
for best security it recommends certificates. Public clients such as native apps and single-page apps must not use
secrets or certificates here.
invalid_client: "Client authentication failed. The client credentials aren't valid. To fix, the
Application Administrator updates the credentials." The user sees only "sign-in failed" on your side.
Step 5: Entra returns tokens
The reply is a block of data (JSON) with these parts, per Microsoft:
| Field | What it is | Present when |
|---|---|---|
| access_token | The token to present to an API | Always (on success) |
| token_type | Always Bearer | Always |
| expires_in | How long the access token is valid, in seconds | Always |
| scope | The scopes the access token is valid for | Optional |
| refresh_token | A long-lived token used to get new access tokens | Only if the offline_access scope was requested |
| id_token | Proof of who signed in | Only if the openid scope was requested |
Step 6: your system checks the ID token and starts a session
Microsoft says that web apps must validate ID tokens that reach them through the browser (the hybrid flow), and libraries validate them in every case. In outline, your system
checks the token's signature against the public keys published in the tenant's OpenID metadata document, and then
checks claims such as iss (who issued it and for which tenant), aud (that it was issued for
your application), exp (not expired), and nonce (matches what was sent). Libraries do this for you.
Entra rotates its signing keys periodically, so a system has to fetch the key set again rather than cache one forever;
Microsoft suggests checking for key updates about every 24 hours.
The Tokens, Side by Side
| ID token | Access token | Refresh token | |
|---|---|---|---|
| Purpose | Proof of authentication: who signed in | Permission for authorization: what the caller may do on an API | Gets new access tokens without asking the user again |
| For whom | Your application (aud is its client ID) | The API (aud is the API); your client treats it as opaque | Your client, presented only to Entra's token endpoint |
| Format | Signed JWT | Often a JWT, but for Microsoft's own APIs (such as Microsoft Graph) it may not be readable by you | Opaque |
| Lifetime | Short; the exp claim states it | Short and variable: a random value between 60 and 90 minutes by default, 75 on average; expires_in tells you | Long, with no fixed lifetime for web apps, but it can expire, be revoked or lack privileges; single-page apps get 24 hours |
| If it expires | The user signs in again (often silently) | Use the refresh token, or ask again | The user (or app) has to sign in again from the start |
Remember the ID token and access token division from Chapter 1: who are you versus what may you do. Chapter 4 returns to these lifetimes, and the important distinction between a credential expiring (the client secret) and a token expiring.
The claims in an ID token
A claim is a statement the identity provider makes about the user or the token. In an ID token the most useful ones for support are:
| Claim | Meaning (Microsoft's definitions, simplified) | Why a support engineer cares |
|---|---|---|
| iss | Issuer: who created the token. Also identifies the tenant for which the user was authenticated | A token from the wrong tenant is rejected |
| aud | Audience: the intended recipient. In an ID token it is your application's client ID, and a token that doesn't match must be rejected | Mismatch means the wrong app, or a wrong client ID in your settings |
| tid | The tenant the user signed in to | Confirms which client tenant this is |
| oid | The immutable object ID of the user's account; the same in every app, different in each tenant | A stable way to identify the user |
| sub | The subject: immutable, but different for each application | Also stable; specific to your app |
| iat, nbf, exp | When the authentication happened, the time before which the token isn't valid, and the time on or after which it can't be accepted | Clock problems and expiry show up here |
| nonce | Echoes the value your app sent | Mismatch means a replayed or mixed-up request |
| name, preferred_username, email | Human-readable details; need the profile or email scope; the values can change | Display only; see below |
| roles, groups | Roles assigned to the user; group membership (with an "overage" fallback) | Used in some authorization decisions |
preferred_username and email are mutable and that the email isn't guaranteed to be correct;
they must not be used for authorization decisions or to identify a user. The stable identifiers are oid (or
sub), with tid for the tenant. If your system matched people by email and someone's name or address
changed, or a new employee received a former colleague's address, you can see "this user can't sign in" or even "this user
sees someone else's account". When one user is affected and everything else looks fine, check what your system uses to match them.
jwt.ms for validation and debugging. A real token, though, is a live
credential until it expires: anyone holding it can use it. Don't paste real tokens into tickets, chats or online
tools, and ask clients not to either. Record the claim names and values that matter (tid, aud, exp) rather than the
token. Microsoft also says not to validate or read tokens for APIs you don't own, such as Graph.
Reading an Error From Entra
When a token request fails, Entra returns an error in a standard shape. This is Microsoft's own example (the values are samples):
| Part | What to do with it |
|---|---|
| error | A short category (invalid_client, invalid_grant, ...). Tells you the kind of problem |
| error_description | The human-readable explanation, starting with an AADSTS code. Carries most of the useful information |
| error_codes | The number from the AADSTS code, on its own (70011 in the example) |
| timestamp | When it happened (compare with the sign-in logs; Chapter 7) |
| trace_id, correlation_id | Always copy these onto the ticket. They identify this request, so the client's administrator can find the matching entry in the sign-in logs |
The error codes at the token step
| error | Microsoft's description | Likely meaning for support |
|---|---|---|
| invalid_client | Client authentication failed; the client credentials aren't valid | Secret or certificate expired, deleted, wrong or not updated (Chapters 4 and 8) |
| invalid_grant | The authorization code or PKCE code verifier is invalid or has expired | Code used too slowly or twice; a bug or a clock/session problem. Start sign-in again |
| unauthorized_client | The client isn't permitted to use this grant type; usually the application isn't registered or isn't added to the user's tenant | Application missing from the client's tenant, or deleted (Chapter 2) |
| invalid_scope | The scope requested by the app is invalid | A misconfigured scope or resource |
| consent_required | The request requires consent for a scope the client lacks permission to request | Permissions or consent changed (Chapter 6) |
| interaction_required | Another step, such as extra authentication, is needed | A policy now needs MFA or similar (Chapter 6) |
| temporarily_unavailable / server_error | Temporary conditions on the server | Retry; check Microsoft service health if widespread |
AADSTS number, a trace ID and a correlation ID? Which error category does it
sound like? That alone often tells you which step failed. Chapters 7 and 8 use your example, with identifiers redacted.
Pattern 2: What Passes in a SAML Sign-in
SAML follows the same shape with different messages. Your system (the "service provider") sends the browser to Entra with a sign-in request; Entra authenticates the user and sends the browser back to your Reply URL (the Assertion Consumer Service) with a signed SAML token. There is no separate server-to-server step and no client secret: the trust rests on the signing certificate. Your system has Entra's certificate (Chapter 2) and checks the signature on every assertion.
What the SAML token carries, according to Microsoft:
- the subject, also called the name identifier (
nameID), which identifies the user; by default Entra puts the user's username (user principal name) there, and if the sign-in request includes aNameIDPolicywith a specific format, Entra honours that format; - an attribute statement of claims; by default these include the user's email address, first name and last name;
- the issuer and audience (your Identifier / Entity ID), and the signature; and
- group claims, only if configured, subject to the 150-group limit for SAML.
Claims are configured under Entra ID > Enterprise apps > All applications > (the app) > Single sign-on > Attributes
& Claims > Edit, as at least a Cloud Application Administrator. The classic SAML failure after a certificate renewal: the client
renews or rotates the signing certificate in Entra, but your system still holds the old one, so every signature check fails
for every user at once. The other classic one: the nameID is a username but your system expects an email address (or the
other way round), so the user is authenticated yet can't be matched to an account.
Pattern 3: What Passes With API Access (Client Credentials)
This is the same step-4 conversation, without any user or browser. Your system asks the token endpoint for a token for itself:
The scope is the resource followed by /.default, meaning "all the application permissions an administrator has granted to
this app for that resource". The reply carries no refresh token and no ID token; when the access token expires, the
application simply asks again using its own credential. So if the credential has expired, the next request fails and the
job stops. This is why background jobs fail at a particular moment, often overnight, with nobody signed in.
Pattern 4: What Passes in Provisioning (SCIM)
Here Entra sends requests to your system. The provisioning service connects to your SCIM 2.0 endpoint, using the credential that was entered in the client's tenant, and uses the SCIM user schema and REST calls to manage users (and groups where supported), over HTTPS with TLS 1.2. Which events cause which calls, according to Microsoft:
| What happens in the tenant | What Entra does to your system |
|---|---|
| A user is assigned to the app (or comes into scope) and no match exists | Creates the user, using the attributes in the attribute mapping |
| A matching user already exists | Updates it; the "matching attribute" (for example userPrincipalName mapped to userName) decides which user is which |
| A user is unassigned, falls out of scope, is disabled or soft-deleted | By default, disables them: for SCIM apps a disable sets the active property to false |
| A user is hard-deleted (in Entra, 30 days after soft-delete) | Deletes the user, if the delete action is enabled |
Entra runs an initial cycle over everything in scope and then repeated incremental cycles, remembering the ID your system gives each user. It records what it did, and what your system answered, in the provisioning logs. If the connection credential is rejected, most calls fail, the job goes into quarantine (Chapter 2), and your own server logs will show refused calls, not Entra sign-ins. Provisioning has no user, no browser and no tokens to look at: just calls to your system, and your system's answers.
Where Can Each Step Fail?
This table pulls the chapter together. Use it as a first guess; the evidence in Chapter 7 confirms it.
| Step | Fails when | Typical symptom |
|---|---|---|
| 1. Redirect to Entra | The redirect URI is not registered exactly as sent; the application isn't in the tenant | An error page from Microsoft before any sign-in; unauthorized_client |
| 2. User signs in | Wrong credentials, MFA, a Conditional Access block, no consent, user not assigned | One user or group affected; a Microsoft error page after the sign-in |
| 3. Redirect back | The user cancels or consent is refused; the returned state doesn't match what was sent | An error such as access_denied arrives at your redirect URI |
| 4. Redeem the code (credential used) | Secret or certificate expired, removed, or changed on one side only | Everyone at that client fails together with invalid_client. If it fails now and then with invalid_grant, the code was late or reused |
| 5. Tokens returned | Requested scopes or consent no longer valid | invalid_scope, consent_required |
| 6. Your system validates | Wrong audience or issuer, bad signature, expired token, keys not refreshed, user can't be matched | Entra succeeded but your system still refuses; check your own logs |
| SAML: signature check | Signing certificate renewed in Entra but not updated in your system | Everyone at that client fails; a signature error in your logs |
| API access: each request | Credential expired; permissions removed | A background job stops; no user is involved |
| Provisioning: Entra calls you | The credential you issued expired, was rotated, or your endpoint is down | Quarantine; users not created or removed |
Hands-On Exercises
All three use fictional values. Never practise with real tokens, secrets or tenant details.
A support engineer wrote down six events from a fictional "Sign in with Microsoft" attempt to Acme Support Desk by a user at Fabrikam, but not in order. Put them in order, name the step each belongs to, say whether it goes through the browser or directly between servers, and say at which event an expired client secret would show up.
- Acme's server sends a request to the tenant's
/oauth2/v2.0/tokenwith the code, its client ID and its client secret. - The browser arrives at
https://acme.example/auth/callback?code=...&state=... - Acme checks the ID token's signature,
aud,issandexp, then sets its own session cookie. - The browser is sent to
login.microsoftonline.com/<fabrikam-tenant>/oauth2/v2.0/authorize?client_id=...&redirect_uri=https://acme.example/auth/callback&scope=openid profile&state=... - Entra replies with JSON containing
access_token,id_tokenandexpires_in. - The user types a password and approves an MFA prompt on a Microsoft page.
Here is the decoded payload of a fictional ID token, with what your system expects. Identify each claim, decide which claim to use to recognise the user, and find at least three reasons this token should be rejected for a Fabrikam sign-in to Acme Support Desk (there are four).
Below are six fictional error responses, shortened to their error value and the first words of the description (AADSTS<n> stands for a number you would copy from the real message). For each, say which step failed, who must act (client administrator, your team or the user), and what you'd record from the message before doing anything else.
invalid_client: "AADSTS<n>: ... client credentials ..."invalid_grant: "AADSTS<n>: ... authorization code ... expired ..."unauthorized_client: "AADSTS<n>: ... application not found in the directory ..."invalid_scope: "AADSTS<n>: ... the provided value for 'scope' is not valid ..."consent_required: "AADSTS<n>: ... requires consent ..."interaction_required: "AADSTS<n>: ... additional authentication required ..."
Chapter 3 Quick Reference
- OIDC sign-in in six steps: redirect to
/authorize; user signs in; code returned; server redeems code with its credential; tokens returned; your system validates the ID token redirect_urimust exactly match a registered redirect URI; the authorization code lives about one minute- The credential (secret, or certificate assertion) is first checked in step 4: an expired one gives
invalid_clientfor everyone at once - ID token = who signed in (for your app); access token = what the caller may do (for the API, opaque to the client); refresh token = renews access tokens
- Access token default lifetime: random 60 to 90 minutes (75 average); use
expires_in; refresh tokens are long-lived but can expire or be revoked - Identify users by
oid(orsub) withtid, never by email or preferred_username - Error shape:
error,error_description(AADSTS code),error_codes,timestamp,trace_id,correlation_id: copy the IDs onto the ticket - SAML: no secret, trust rests on the signing certificate; NameID defaults to the username (UPN); group limit 150 for SAML, 200 for JWT
- Client credentials: no user, no refresh token, scope is
resource/.default; an expired credential stops the job at the next request - SCIM: Entra calls your system; disables via
active= false; failures appear in the provisioning logs and as quarantine - Tokens are credentials: never put real ones on tickets or online decoders