Add confidential client troubleshooting guide - #760
Conversation
New page covering common issues in confidential client apps: - Throttling (HTTP 429 / AADSTS50196) - Network instability and socket exceptions - On-Behalf-Of (OBO) failures - Client credential errors (invalid secret, expired cert, app not found) - Token cache miss diagnosis - Managed Identity failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Learn Build status updates of commit b9d6304: ✅ Validation status: passed
For more details, please refer to the build report. |
PoliCheck Scan ReportThe following report lists PoliCheck issues in PR files. Before you merge the PR, you must fix all severity-1 and severity-2 issues. The AI Review Details column lists suggestions for either removing or replacing the terms. If you find a false positive result, mention it in a PR comment and include this text: #policheck-false-positive. This feedback helps reduce false positives in future scans. ✅ No issues foundMore information about PoliCheckInformation: PoliCheck | Severity Guidance | Term |
|
|
||
| Verify if there are any recent updates to network or firewall rules that might have caused connectivity issues to `login.microsoftonline.com` and regional endpoints. | ||
|
|
||
| ## On-Behalf-Of (OBO) failures |
There was a problem hiding this comment.
there are also cases where a UiRequiredException is thrown and the user needs to handle conditional access by satisfying the provided access policies in the claims.
There was a problem hiding this comment.
Added a new subsection under On-Behalf-Of (OBO) failures called MsalUiRequiredException — conditional access, MFA, or incremental consent. It covers the symptoms (AcquireTokenOnBehalfOf throwing MsalUiRequiredException with Claims set), the causes (CA policy on the downstream resource, incremental consent), and the resolution: the web API returns HTTP 401 with a WWW-Authenticate header carrying ex.Claims, and the client re-acquires a token with .WithClaims(). It links to the Handling MFA, conditional access and incremental consent section you referenced.
Travis Walker (trwalke)
left a comment
There was a problem hiding this comment.
Should handle claims challenge for conditional access. otherwise LGTM
|
|
||
| `MsalServiceException` with `AADSTS7000215: Invalid client secret provided`. | ||
|
|
||
| #### Resolution |
There was a problem hiding this comment.
My understanding is that we wanted to move away from client secrets due to security. Should probable add a comment here encouraging customers to use certs or some other auth method.
There was a problem hiding this comment.
Added an [!IMPORTANT] note at the top of the Client credential errors section. It calls out that client secrets are the least secure option and gives a preference order: managed identity when running on Azure, federated identity credentials (workload identity federation) when running outside Azure, then certificates / signed client assertions. It closes with guidance to use secrets only for local development and never to check them into source control. Also added a pointer to that note from the AADSTS7000215 resolution.
|
|
||
| #### Symptoms | ||
|
|
||
| `MsalServiceException` with error code `AADSTS50013: Assertion failed signature validation` or `invalid_grant`. |
There was a problem hiding this comment.
I think it's worth stating that apps should not try to extract the AADSTS error codes and to handle these dynamically. The codes are just references.
The exception types are sufficient for dynamic processing, e.g. UiRequiredException - need to reprompt the user, ClaimsExcepton - need to add claims.
There was a problem hiding this comment.
Good call. Added an [!IMPORTANT] note right under the article intro: the AADSTS codes in the article are references for humans diagnosing a problem, they aren't a stable API, and apps shouldn't extract or branch on them programmatically. It directs readers to branch on the MSAL exception type instead — catch MsalUiRequiredException to reprompt, and check MsalServiceException.Claims to send a claims challenge. Repeated the same point in the new OBO conditional access section.
There was a problem hiding this comment.
Pull request overview
This PR adds a new documentation page focused on troubleshooting MSAL.NET confidential client scenarios (service-to-service, web apps/APIs), and wires it into the docs navigation under Advanced > Handling exceptions and errors.
Changes:
- Added a new troubleshooting guide covering throttling, networking/socket issues, OBO failures, client credential errors, cache miss diagnosis, and managed identity failures.
- Updated the docs TOC to include the new troubleshooting page under the exceptions/error handling section.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| msal-dotnet-articles/TOC.yml | Adds a TOC entry pointing to the new confidential client troubleshooting page. |
| msal-dotnet-articles/advanced/exceptions/confidential-client-troubleshooting.md | Introduces the new troubleshooting guide content and code samples for diagnosing common confidential client issues. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| TimeSpan delay = ex.Headers.RetryAfter?.Delta | ||
| ?? TimeSpan.FromSeconds(ex.Headers.RetryAfter?.Date?.Subtract(DateTimeOffset.Now).TotalSeconds ?? 60); | ||
| await Task.Delay(delay); |
There was a problem hiding this comment.
Fixed. The sample now handles Delta and Date as separate cases with pattern matching (retryAfter?.Delta is TimeSpan delta / retryAfter?.Date is DateTimeOffset date), so the nullable is unwrapped properly and it compiles. It defaults to 60 seconds when neither is present, computes date - DateTimeOffset.UtcNow for the absolute form, and clamps the result with if (delay < TimeSpan.Zero) delay = TimeSpan.Zero; so clock skew can never pass a negative value to Task.Delay.
| 1. Ensure the required downstream API permissions are declared in your app registration under **API permissions**. | ||
| 2. For multi-tenant apps, trigger admin consent using the admin consent URL: | ||
| ``` | ||
| https://login.microsoftonline.com/{tenant}/adminconsent?client_id={clientId} |
There was a problem hiding this comment.
Added. The admin consent URL is now https://login.microsoftonline.com/{tenant}/adminconsent?client_id={clientId}&redirect_uri={redirectUri}, with an explanation that redirect_uri is required, must be URL-encoded, and must exactly match a reply URL registered on the app registration under Authentication — Microsoft Entra ID redirects the admin back there after consent, so the request fails if it's missing or doesn't match.
There was a problem hiding this comment.
Neha Bhargava (@neha-bhargava)
I've made comments to address blocking issues according to the PR Review quality criteria. Please address all suggestions before you sign off.
There was a problem hiding this comment.
For SEO, change the file name to confidential-client-troubleshoot.md
There was a problem hiding this comment.
Renamed the file to confidential-client-troubleshoot.md and updated the href in msal-dotnet-articles/TOC.yml. I checked for other cross-links to the old name and there are none. On .openpublishing.redirection.json: the entries in this repo are all for previously published files, and this article is brand new and has never been published, so no redirect entry is needed. Happy to add one if you'd prefer it for safety.
| 1. **Missing or misconfigured token cache** — Every call goes to Microsoft Entra ID instead of serving tokens from the cache. | ||
| 2. **Requesting tokens in a tight loop** — For example, calling `AcquireTokenForClient` on every incoming request without checking the cache first. | ||
| 3. **Too many distinct scopes/resources** — Each unique scope produces a separate cached token. |
There was a problem hiding this comment.
| 1. **Missing or misconfigured token cache** — Every call goes to Microsoft Entra ID instead of serving tokens from the cache. | |
| 2. **Requesting tokens in a tight loop** — For example, calling `AcquireTokenForClient` on every incoming request without checking the cache first. | |
| 3. **Too many distinct scopes/resources** — Each unique scope produces a separate cached token. | |
| - **Missing or misconfigured token cache** — Every call goes to Microsoft Entra ID instead of serving tokens from the cache. | |
| - **Requesting tokens in a tight loop** — For example, calling `AcquireTokenForClient` on every incoming request without checking the cache first. | |
| - **Too many distinct scopes/resources** — Each unique scope produces a separate cached token. |
Nonsequential lists should be bulleted, per MS style.
There was a problem hiding this comment.
Applied your suggestion verbatim — the causes list under Throttling is now bulleted.
|
|
||
| ### Common causes | ||
|
|
||
| 1. **Missing or misconfigured token cache** — Every call goes to Microsoft Entra ID instead of serving tokens from the cache. |
There was a problem hiding this comment.
If this list is not sequential, make it bulleted instead of numbered.
There was a problem hiding this comment.
Applied your suggestion verbatim — the causes list under Network instability and socket exceptions is now bulleted.
| 1. **Not caching tokens** — Without caching, every token request results in a network call to Microsoft Entra ID. This increases exposure to transient network failures and socket exhaustion. | ||
| 2. **Service outage or local network issues** — The token endpoint may be temporarily unavailable, or the local network is unstable. | ||
| 3. **Custom `HttpClient` overriding MSAL's default** — MSAL's built-in `HttpClient` is designed to be scalable. If you override it, connection management becomes your responsibility. | ||
| 4. **Firewall or network rules** — Recent updates to network or firewall rules may be blocking outbound traffic to `login.microsoftonline.com`. |
There was a problem hiding this comment.
| 1. **Not caching tokens** — Without caching, every token request results in a network call to Microsoft Entra ID. This increases exposure to transient network failures and socket exhaustion. | |
| 2. **Service outage or local network issues** — The token endpoint may be temporarily unavailable, or the local network is unstable. | |
| 3. **Custom `HttpClient` overriding MSAL's default** — MSAL's built-in `HttpClient` is designed to be scalable. If you override it, connection management becomes your responsibility. | |
| 4. **Firewall or network rules** — Recent updates to network or firewall rules may be blocking outbound traffic to `login.microsoftonline.com`. | |
| - **Not caching tokens** — Without caching, every token request results in a network call to Microsoft Entra ID. This increases exposure to transient network failures and socket exhaustion. | |
| - **Service outage or local network issues** — The token endpoint may be temporarily unavailable, or the local network is unstable. | |
| - **Custom `HttpClient` overriding MSAL's default** — MSAL's built-in `HttpClient` is designed to be scalable. If you override it, connection management becomes your responsibility. | |
| - **Firewall or network rules** — Recent updates to network or firewall rules may be blocking outbound traffic to `login.microsoftonline.com`. |
There was a problem hiding this comment.
Applied your suggestion verbatim — the AADSTS50013 causes list is now bulleted.
| 1. The incoming token (user assertion) has expired. | ||
| 2. The token was issued by a different authority than expected. | ||
| 3. The audience (`aud`) of the token doesn't match the app's client ID or app ID URI. |
There was a problem hiding this comment.
| 1. The incoming token (user assertion) has expired. | |
| 2. The token was issued by a different authority than expected. | |
| 3. The audience (`aud`) of the token doesn't match the app's client ID or app ID URI. | |
| - The incoming token (user assertion) has expired. | |
| - The token was issued by a different authority than expected. | |
| - The audience (`aud`) of the token doesn't match the app's client ID or app ID URI. |
There was a problem hiding this comment.
Converted to a bulleted list — these are independent things to check, not ordered steps. Also added a bullet pointing at the new note recommending managed identity, federated credentials, or certificates over secrets.
|
|
||
| #### Resolution | ||
|
|
||
| 1. Verify the secret value (not the secret ID) is used in your configuration. |
There was a problem hiding this comment.
Should this list be bulleted rather than numbered?
There was a problem hiding this comment.
Converted to a bulleted list — nonsequential.
|
|
||
| #### Resolution | ||
|
|
||
| 1. Check certificate expiration: ensure the certificate's `NotAfter` date hasn't passed. |
There was a problem hiding this comment.
Should this list be bulleted rather than numbered?
There was a problem hiding this comment.
Applied your suggestion verbatim — the AADSTS700016 causes list is now bulleted.
| 1. Wrong `ClientId` in configuration. | ||
| 2. The app registration exists in a different tenant than the authority being used. | ||
| 3. For multi-tenant apps, the app hasn't been consented to in the target tenant. |
There was a problem hiding this comment.
| 1. Wrong `ClientId` in configuration. | |
| 2. The app registration exists in a different tenant than the authority being used. | |
| 3. For multi-tenant apps, the app hasn't been consented to in the target tenant. | |
| - Wrong `ClientId` in configuration. | |
| - The app registration exists in a different tenant than the authority being used. | |
| - For multi-tenant apps, the app hasn't been consented to in the target tenant. |
There was a problem hiding this comment.
Applied your suggestion verbatim — the managed identity / IMDS causes list is now bulleted.
| 1. The application is not running in an Azure environment that supports managed identity (e.g., running locally or in an unsupported hosting environment). | ||
| 2. Network Security Group (NSG) rules block access to the IMDS endpoint (`169.254.169.254`). | ||
| 3. A user-assigned managed identity ID is specified but doesn't exist or isn't assigned to the resource. |
There was a problem hiding this comment.
| 1. The application is not running in an Azure environment that supports managed identity (e.g., running locally or in an unsupported hosting environment). | |
| 2. Network Security Group (NSG) rules block access to the IMDS endpoint (`169.254.169.254`). | |
| 3. A user-assigned managed identity ID is specified but doesn't exist or isn't assigned to the resource. | |
| - The application is not running in an Azure environment that supports managed identity (e.g., running locally or in an unsupported hosting environment). | |
| - Network Security Group (NSG) rules block access to the IMDS endpoint (`169.254.169.254`). | |
| - A user-assigned managed identity ID is specified but doesn't exist or isn't assigned to the resource. |
There was a problem hiding this comment.
Converted to a bulleted list, and reindented the nested code sample to match. I also audited the rest of the article and converted the other nonsequential numbered lists to bullets (the throttling Resolution list, the AADSTS65001 consent resolution, and the AADSTS700024 resolution). I kept numbering only where the list is a genuine ordered procedure — the Diagnostic steps under token cache miss, and the two-step API-then-client sequence in the new OBO conditional access section.
|
IMPORTANT: When the changes are ready for publication, adding a #label:"aq-pr-triaged" |
- Rename article to confidential-client-troubleshoot.md for SEO and update TOC - Add OBO MsalUiRequiredException / conditional access claims-challenge section - Add note steering customers off client secrets toward MI, FIC, and certificates - Note that AADSTS codes are human references; branch on exception types instead - Fix Retry-After sample so it compiles and never passes a negative delay - Add required redirect_uri to the admin consent URL and explain the requirement - Convert nonsequential numbered lists to bulleted lists per Microsoft style - Update redirecting learn.microsoft.com links to their final /entra destinations Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
PoliCheck Scan ReportThe following report lists PoliCheck issues in PR files. Before you merge the PR, you must fix all severity-1 and severity-2 issues. The AI Review Details column lists suggestions for either removing or replacing the terms. If you find a false positive result, mention it in a PR comment and include this text: #policheck-false-positive. This feedback helps reduce false positives in future scans. ✅ No issues foundMore information about PoliCheckInformation: PoliCheck | Severity Guidance | Term |
|
Learn Build status updates of commit 82c279d: ✅ Validation status: passed
For more details, please refer to the build report. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
PoliCheck Scan ReportThe following report lists PoliCheck issues in PR files. Before you merge the PR, you must fix all severity-1 and severity-2 issues. The AI Review Details column lists suggestions for either removing or replacing the terms. If you find a false positive result, mention it in a PR comment and include this text: #policheck-false-positive. This feedback helps reduce false positives in future scans. ✅ No issues foundMore information about PoliCheckInformation: PoliCheck | Severity Guidance | Term |
|
Learn Build status updates of commit 44db486: ✅ Validation status: passed
For more details, please refer to the build report. |
Summary
Adds a new troubleshooting page for confidential client applications under Advanced > Handling exceptions and errors.
Topics covered
Motivation
These scenarios were identified as gaps in the existing MSAL.NET documentation. The current exception/error handling docs focus primarily on public client (desktop/mobile) flows. This page provides equivalent guidance for service-to-service scenarios that are common in production workloads.
Changes