Skip to content

Add confidential client troubleshooting guide - #760

Merged
Bogdan Gavril (bgavrilMS) merged 3 commits into
livefrom
docs/confidential-client-troubleshooting
Aug 26, 2026
Merged

Add confidential client troubleshooting guide#760
Bogdan Gavril (bgavrilMS) merged 3 commits into
livefrom
docs/confidential-client-troubleshooting

Conversation

@neha-bhargava

Copy link
Copy Markdown
Contributor

Summary

Adds a new troubleshooting page for confidential client applications under Advanced > Handling exceptions and errors.

Topics covered

  • Throttling (HTTP 429 / AADSTS50196) — detecting cache misses, respecting Retry-After
  • Network instability and socket exceptions — token caching as primary mitigation, HttpClient guidance
  • On-Behalf-Of (OBO) failures — AADSTS50013, consent issues, token size
  • Client credential errors — invalid secret (AADSTS7000215), expired cert (AADSTS700024), app not found (AADSTS700016)
  • Token cache miss diagnosis — CacheRefreshReason table, distributed cache verification
  • Managed Identity failures — IMDS timeout, federated identity credential mismatch

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

  • \msal-dotnet-articles/advanced/exceptions/confidential-client-troubleshooting.md\ — new page
  • \msal-dotnet-articles/TOC.yml\ — added TOC entry under 'Handling exceptions and errors'

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>
@neha-bhargava
Neha Bhargava (neha-bhargava) requested a review from a team as a code owner May 19, 2026 18:54
@learn-build-service-prod

Copy link
Copy Markdown
Contributor

Learn Build status updates of commit b9d6304:

✅ Validation status: passed

File Status Preview URL Details
msal-dotnet-articles/advanced/exceptions/confidential-client-troubleshooting.md ✅Succeeded View
msal-dotnet-articles/TOC.yml ✅Succeeded View

For more details, please refer to the build report.

@learn-build-service-prod

Copy link
Copy Markdown
Contributor

PoliCheck Scan Report

The 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 found

More information about PoliCheck

Information: PoliCheck | Severity Guidance | Term
For any questions: Try searching the learn.microsoft.com contributor guides or post your question in the Learn support channel.


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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

See MsalUiRequiredException

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@trwalke Travis Walker (trwalke) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should handle claims challenge for conditional access. otherwise LGTM


`MsalServiceException` with `AADSTS7000215: Invalid client secret provided`.

#### Resolution

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@bgavrilMS Bogdan Gavril (bgavrilMS) May 21, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +56 to +58
TimeSpan delay = ex.Headers.RetryAfter?.Delta
?? TimeSpan.FromSeconds(ex.Headers.RetryAfter?.Date?.Subtract(DateTimeOffset.Now).TotalSeconds ?? 60);
await Task.Delay(delay);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ShannonLeavitt Shannon Leavitt (ShannonLeavitt) left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For SEO, change the file name to confidential-client-troubleshoot.md

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +30 to +32
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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this list is not sequential, make it bulleted instead of numbered.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied your suggestion verbatim — the causes list under Network instability and socket exceptions is now bulleted.

Comment on lines +77 to +80
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`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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`.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied your suggestion verbatim — the AADSTS50013 causes list is now bulleted.

Comment on lines +117 to +119
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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this list be bulleted rather than numbered?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converted to a bulleted list — nonsequential.


#### Resolution

1. Check certificate expiration: ensure the certificate's `NotAfter` date hasn't passed.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this list be bulleted rather than numbered?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied your suggestion verbatim — the AADSTS700016 causes list is now bulleted.

Comment on lines +199 to +201
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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied your suggestion verbatim — the managed identity / IMDS causes list is now bulleted.

Comment on lines +252 to +254
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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ShannonLeavitt

Copy link
Copy Markdown

IMPORTANT: When the changes are ready for publication, adding a #sign-off comment is the best way to signal that the PR is ready for the review team to merge.

#label:"aq-pr-triaged"
@MicrosoftDocs/public-repo-pr-review-team

@ShannonLeavitt Shannon Leavitt (ShannonLeavitt) added the aq-pr-triaged Tracking label for the vendor PR Review team label Jul 8, 2026
- 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>
@learn-build-service-prod

Copy link
Copy Markdown
Contributor

PoliCheck Scan Report

The 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 found

More information about PoliCheck

Information: PoliCheck | Severity Guidance | Term
For any questions: Try searching the learn.microsoft.com contributor guides or post your question in the Learn support channel.

@learn-build-service-prod

Copy link
Copy Markdown
Contributor

Learn Build status updates of commit 82c279d:

✅ Validation status: passed

File Status Preview URL Details
msal-dotnet-articles/advanced/exceptions/confidential-client-troubleshoot.md ✅Succeeded View
msal-dotnet-articles/TOC.yml ✅Succeeded View

For more details, please refer to the build report.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@learn-build-service-prod

Copy link
Copy Markdown
Contributor

PoliCheck Scan Report

The 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 found

More information about PoliCheck

Information: PoliCheck | Severity Guidance | Term
For any questions: Try searching the learn.microsoft.com contributor guides or post your question in the Learn support channel.

@learn-build-service-prod

Copy link
Copy Markdown
Contributor

Learn Build status updates of commit 44db486:

✅ Validation status: passed

File Status Preview URL Details
msal-dotnet-articles/advanced/exceptions/confidential-client-troubleshoot.md ✅Succeeded View
msal-dotnet-articles/TOC.yml ✅Succeeded View

For more details, please refer to the build report.

@bgavrilMS
Bogdan Gavril (bgavrilMS) merged commit 5e7ebd8 into live Aug 26, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

aq-pr-triaged Tracking label for the vendor PR Review team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants