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".

Two kinds of traffic
In a sign-in, some messages go through the user's browser (your system redirects the browser to Entra and Entra redirects it back), and some go directly from your server to Entra (where your system proves its own identity with its credential). A failure in one has different symptoms and evidence from a failure in the other, which is why the next sections number the steps.

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:

User's browser Your system Entra (client's tenant) -------------- ----------- ----------------------- 1. clicks sign in --> redirects the browser ---> /authorize 2. signs the user in (password, MFA, policies, consent check) 3. <------------------------------------ redirects back to your with a short-lived "code" redirect URI code arrives --> 4. server calls /token ----> checks code AND your credential <--- 5. returns tokens 6. validates the ID token, starts its own session

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:

ParameterWhat it says
{tenant} (in the path)Who may sign in: a tenant ID or domain for one organisation, or organizations / common / consumers
client_idThe Application (client) ID of your registration
response_typeMust include code
redirect_uriWhere Entra should send the user back. It must exactly match one of the redirect URIs registered for the application
scopeThe permissions requested; for sign-in, openid (plus profile / email if wanted)
stateA 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
nonceNeeded 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:

POST /<tenant-id>/oauth2/v2.0/token Host: login.microsoftonline.com Content-Type: application/x-www-form-urlencoded client_id=<application-client-id> &scope=<scopes> &code=<the code from step 3> &redirect_uri=<the same redirect URI as step 1> &grant_type=authorization_code &code_verifier=<the PKCE secret> &client_secret=<the application's secret> <-- or a signed certificate assertion instead

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.

This is where an expired credential bites
Everything before step 4 happens in the browser and needs no secret. Step 4 is the first place Entra checks your system's credential. If the client secret has expired, been deleted or been replaced without updating your system, the user can sign in perfectly well at step 2 and still be turned away at step 4. Microsoft lists the matching error for this step as 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:

FieldWhat it isPresent when
access_tokenThe token to present to an APIAlways (on success)
token_typeAlways BearerAlways
expires_inHow long the access token is valid, in secondsAlways
scopeThe scopes the access token is valid forOptional
refresh_tokenA long-lived token used to get new access tokensOnly if the offline_access scope was requested
id_tokenProof of who signed inOnly 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 tokenAccess tokenRefresh token
PurposeProof of authentication: who signed inPermission for authorization: what the caller may do on an APIGets new access tokens without asking the user again
For whomYour application (aud is its client ID)The API (aud is the API); your client treats it as opaqueYour client, presented only to Entra's token endpoint
FormatSigned JWTOften a JWT, but for Microsoft's own APIs (such as Microsoft Graph) it may not be readable by youOpaque
LifetimeShort; the exp claim states itShort and variable: a random value between 60 and 90 minutes by default, 75 on average; expires_in tells youLong, with no fixed lifetime for web apps, but it can expire, be revoked or lack privileges; single-page apps get 24 hours
If it expiresThe user signs in again (often silently)Use the refresh token, or ask againThe 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:

ClaimMeaning (Microsoft's definitions, simplified)Why a support engineer cares
issIssuer: who created the token. Also identifies the tenant for which the user was authenticatedA token from the wrong tenant is rejected
audAudience: the intended recipient. In an ID token it is your application's client ID, and a token that doesn't match must be rejectedMismatch means the wrong app, or a wrong client ID in your settings
tidThe tenant the user signed in toConfirms which client tenant this is
oidThe immutable object ID of the user's account; the same in every app, different in each tenantA stable way to identify the user
subThe subject: immutable, but different for each applicationAlso stable; specific to your app
iat, nbf, expWhen the authentication happened, the time before which the token isn't valid, and the time on or after which it can't be acceptedClock problems and expiry show up here
nonceEchoes the value your app sentMismatch means a replayed or mixed-up request
name, preferred_username, emailHuman-readable details; need the profile or email scope; the values can changeDisplay only; see below
roles, groupsRoles assigned to the user; group membership (with an "overage" fallback)Used in some authorization decisions
A classic support trap: matching users by email or username
Microsoft warns that 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.
Group claims and the overage limit
To keep tokens small, Entra limits how many groups it lists. Per Microsoft the limits are 150 for SAML tokens and 200 for JWTs: a user in more groups than that gets no group list in the token, only a pointer telling the application to ask Microsoft Graph. If a system grants access by group and a long-standing user suddenly loses access after joining more groups, this is one of the things to rule out.
Tokens are credentials: handle them like passwords
Microsoft says developers can decode JWTs on 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):

{ "error": "invalid_scope", "error_description": "AADSTS70011: The provided value for the input parameter 'scope' is not valid. ... Trace ID: 0000aaaa-11bb-cccc-dd22-eeeeee333333 Correlation ID: aaaa0000-bb11-2222-33cc-444444dddddd Timestamp: 2016-01-09 02:02:12Z", "error_codes": [ 70011 ], "timestamp": "2016-01-09 02:02:12Z", "trace_id": "0000aaaa-11bb-cccc-dd22-eeeeee333333", "correlation_id": "aaaa0000-bb11-2222-33cc-444444dddddd" }
PartWhat to do with it
errorA short category (invalid_client, invalid_grant, ...). Tells you the kind of problem
error_descriptionThe human-readable explanation, starting with an AADSTS code. Carries most of the useful information
error_codesThe number from the AADSTS code, on its own (70011 in the example)
timestampWhen it happened (compare with the sign-in logs; Chapter 7)
trace_id, correlation_idAlways 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

errorMicrosoft's descriptionLikely meaning for support
invalid_clientClient authentication failed; the client credentials aren't validSecret or certificate expired, deleted, wrong or not updated (Chapters 4 and 8)
invalid_grantThe authorization code or PKCE code verifier is invalid or has expiredCode used too slowly or twice; a bug or a clock/session problem. Start sign-in again
unauthorized_clientThe client isn't permitted to use this grant type; usually the application isn't registered or isn't added to the user's tenantApplication missing from the client's tenant, or deleted (Chapter 2)
invalid_scopeThe scope requested by the app is invalidA misconfigured scope or resource
consent_requiredThe request requires consent for a scope the client lacks permission to requestPermissions or consent changed (Chapter 6)
interaction_requiredAnother step, such as extra authentication, is neededA policy now needs MFA or similar (Chapter 6)
temporarily_unavailable / server_errorTemporary conditions on the serverRetry; check Microsoft service health if widespread
If you have a real "validate the connection" error
Compare it to this structure: is there an 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 a NameIDPolicy with 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:

POST /<tenant-id>/oauth2/v2.0/token Content-Type: application/x-www-form-urlencoded client_id=<application-client-id> &scope=https://graph.microsoft.com/.default &client_secret=<the application's secret> <-- or client_assertion for a certificate &grant_type=client_credentials Reply: { "token_type": "Bearer", "expires_in": 3599, "access_token": "..." }

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 tenantWhat Entra does to your system
A user is assigned to the app (or comes into scope) and no match existsCreates the user, using the attributes in the attribute mapping
A matching user already existsUpdates 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-deletedBy 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.

StepFails whenTypical symptom
1. Redirect to EntraThe redirect URI is not registered exactly as sent; the application isn't in the tenantAn error page from Microsoft before any sign-in; unauthorized_client
2. User signs inWrong credentials, MFA, a Conditional Access block, no consent, user not assignedOne user or group affected; a Microsoft error page after the sign-in
3. Redirect backThe user cancels or consent is refused; the returned state doesn't match what was sentAn 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 onlyEveryone 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 returnedRequested scopes or consent no longer validinvalid_scope, consent_required
6. Your system validatesWrong audience or issuer, bad signature, expired token, keys not refreshed, user can't be matchedEntra succeeded but your system still refuses; check your own logs
SAML: signature checkSigning certificate renewed in Entra but not updated in your systemEveryone at that client fails; a signature error in your logs
API access: each requestCredential expired; permissions removedA background job stops; no user is involved
Provisioning: Entra calls youThe credential you issued expired, was rotated, or your endpoint is downQuarantine; users not created or removed

Hands-On Exercises

All three use fictional values. Never practise with real tokens, secrets or tenant details.

Exercise 1

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.

  1. Acme's server sends a request to the tenant's /oauth2/v2.0/token with the code, its client ID and its client secret.
  2. The browser arrives at https://acme.example/auth/callback?code=...&state=...
  3. Acme checks the ID token's signature, aud, iss and exp, then sets its own session cookie.
  4. 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=...
  5. Entra replies with JSON containing access_token, id_token and expires_in.
  6. The user types a password and approves an MFA prompt on a Microsoft page.
๐Ÿ“„ View solution
Exercise 2

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).

{ "iss": "https://login.microsoftonline.com/33333333-3333-3333-3333-333333333333/v2.0", "aud": "99999999-9999-9999-9999-999999999999", "tid": "33333333-3333-3333-3333-333333333333", "oid": "55555555-5555-5555-5555-555555555555", "sub": "AbCdEf123", "name": "A. Student", "preferred_username": "a.student@fabrikam.example", "nonce": "xyz789", "exp": "2026-10-01 08:00 UTC" (shown as a date for readability) } Your system expects: Fabrikam's tenant ID ........ 11111111-1111-1111-1111-111111111111 Acme's client ID ............ 22222222-2222-2222-2222-222222222222 The nonce it sent ........... n-0001 The time now ................ 2026-10-01 10:30 UTC
๐Ÿ“„ View solution
Exercise 3

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.

  1. invalid_client: "AADSTS<n>: ... client credentials ..."
  2. invalid_grant: "AADSTS<n>: ... authorization code ... expired ..."
  3. unauthorized_client: "AADSTS<n>: ... application not found in the directory ..."
  4. invalid_scope: "AADSTS<n>: ... the provided value for 'scope' is not valid ..."
  5. consent_required: "AADSTS<n>: ... requires consent ..."
  6. interaction_required: "AADSTS<n>: ... additional authentication required ..."
๐Ÿ“„ View solution

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_uri must 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_client for 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 (or sub) with tid, 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