Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 15, 2025

Fix certificate reload logic to only trigger on certificate-specific errors

  • You've read the Contributor Guide and Code of Conduct.
  • You've included unit or integration tests for your change, where applicable.
  • You've included inline docs for your change, where applicable.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.

Fix certificate reload triggering on all invalid_client errors

Description

PR #3430 broadened certificate reload logic to trigger on all invalid_client errors except AADSTS7000215, causing infinite loops with Agent identities and unnecessary reloads for non-certificate authentication failures (wrong client secret, wrong client ID, etc.).

Core Fix

Changed IsInvalidClientCertificateOrSignedAssertionError from blacklist approach (exclude AADSTS7000215) to whitelist approach (only specific certificate error codes):

Now triggers reload only for:

  • AADSTS700027 - Invalid key
  • AADSTS700024 - Signed assertion invalid time range
  • AADSTS7000214 - Certificate revoked
  • AADSTS1000502 - Certificate outside validity window
  • AADSTS7000274 - Certificate not within validity period
  • AADSTS7000277 - Certificate revoked (alternative)

No longer triggers reload for:

  • AADSTS7000215 - Invalid client secret
  • AADSTS700016 - Application not found
  • AADSTS7000222 - Expired client secret
  • AADSTS50011 - Invalid reply address
  • AADSTS50012 - Invalid client credentials
  • Any other non-certificate invalid_client errors

Changes

  • TokenAcquisition.cs: Rewrote error detection logic with explicit certificate error code checks
  • Constants.cs: Added CertificateNotWithinValidityPeriod and CertificateWasRevoked constants
  • PublicAPI files: Updated for new internal constants across all target frameworks
  • CertificateReloadLogicTests.cs: Added 18 test cases covering all certificate/non-certificate error scenarios
  • CertificatesObserverTests.cs: Updated mock to return certificate-specific error code (AADSTS700027)

Example

// Before: Any invalid_client except AADSTS7000215 triggered reload
// This wrongly reloaded on wrong client ID, expired secrets, etc.

// After: Only specific certificate errors trigger reload
private bool IsInvalidClientCertificateOrSignedAssertionError(MsalServiceException exMsal)
{
    if (_retryClientCertificate || 
        !string.Equals(exMsal.ErrorCode, Constants.InvalidClient, StringComparison.OrdinalIgnoreCase))
        return false;

    string responseBody = exMsal.ResponseBody;
    return responseBody.Contains(Constants.InvalidKeyError, ...) ||
           responseBody.Contains(Constants.CertificateHasBeenRevoked, ...) ||
           // ... other certificate-specific error codes
}

This prevents infinite loops in Agent identity scenarios and eliminates unnecessary reload attempts for non-certificate authentication failures.

Original prompt

This section details on the original issue you should resolve

<issue_title>Certificate reload logic triggers on all invalid_client errors, not just cert-related issues (regression from PR #3430)</issue_title>
<issue_description>### Microsoft.Identity.Web Library

Microsoft.Identity.Web.TokenAcquisition

Microsoft.Identity.Web version

4.x

Web app

Not Applicable

Web API

Not Applicable

Token cache serialization

Not Applicable

Description

The certificate reload logic triggers on all invalid_client errors, not just certificate-related issues. This means it attempts reloads for unrelated problems, such as when the client credentials are not a certificate (e.g., wrong client secret, wrong client ID, missing client ID, etc.), resulting in unnecessary reloads and confusing behavior. PR #3430 broadened the retry logic too far, causing this regression. The intended behavior should only reload certificates for error responses specifically related to certificates, such as certificate expiration or revocation, not in unrelated authentication failures.

❌This causes infinite loops in some cases, especially when used with Agent identities which chain several token acquisitions of signed assertions.

Reproduction steps

  1. Configure an app with invalid client credentials that are NOT a certificate, e.g., wrong client secret or invalid client ID.
  2. Observe that the certificate reload logic triggers and forcibly reloads, even though the error is unrelated certificates.
  3. See unnecessary reloads and application retries that do not address the root cause.
  4. If used .WithAgentIdentity(), observe a possible infinite loop / hang

Alternatively:

Error message

Error example (not certificate related):
AADSTS7000215: Invalid client secret is provided.

Expected only certificate-related errors like:
AADSTS7000274: Certificate is not within its validity period.
AADSTS7000277: Certificate was revoked.

Id Web logs

No response

Relevant code snippets

// After PR AzureAD/microsoft-identity-web#3430, the check became:
private bool IsInvalidClientCertificateOrSignedAssertionError(MsalServiceException exMsal)
{
    return ! _retryClientCertificate &&
        string.Equals(exMsal.ErrorCode, Constants.InvalidClient, StringComparison.OrdinalIgnoreCase);
}

Previously, several error message checks ensured the retry was only for certificate-related causes. 

// Example error that wrongly triggers reload:
// This triggers a reload, but is unrelated to certificates.
MsalServiceException: 
❌ AADSTS7000215 - Invalid client secret (not certificate-related)AADSTS700016 - Application not found / wrong Client ID 
❌ AADSTS7000222 - Invalid client secret provided (expired secret) 
❌ AADSTS50011 - Invalid reply address configured as cases where the reload should not be triggered?

and possibly AADSTS50012 - Invalid client credentials (various causes)

Regression

Last worked when error check was limited to certificate errors, before PR #3430.

Expected behavior

Certificate reload logic should ONLY be triggered for errors directly related to the certificate—such as revocation, expiration, or an invalid client assertion signature. It should NOT be triggered for generic invalid_client errors like wrong client secret, missing client ID, or misconfigured credentials. The error filtering must be precise to prevent incorrect reloads and unnecessary application retries.</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Fix certificate reload logic for specific invalid_client errors Fix certificate reload logic to only trigger on certificate-specific errors Dec 15, 2025
Copilot AI requested a review from jmprieur December 15, 2025 03:56
@jmprieur jmprieur marked this pull request as ready for review December 15, 2025 04:09
@jmprieur jmprieur requested a review from a team as a code owner December 15, 2025 04:09
Copy link
Member

@bgavrilMS bgavrilMS left a comment

Choose a reason for hiding this comment

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

Do we have agreement from eSTS that the error codes are stable? Afaik, error codes are not part of the public contract between SDK and service. The existing logic is safer.

internal const string SignedAssertionInvalidTimeRange = "AADSTS700024";
internal const string CertificateHasBeenRevoked = "AADSTS7000214";
internal const string CertificateIsOutsideValidityWindow = "AADSTS1000502";
internal const string CertificateNotWithinValidityPeriod = "AADSTS7000274";
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe that this is "Client assertion contains an invalid signature", not CertificateNotWithinValidityPeriod. This makes sense as right now this is the same as CertificateIsOutsideValidityWindow which is right above it. https://stackoverflow.com/questions/62716173/jwt-token-error-aadsts700027-client-assertion-contains-an-invalid-signature

responseBody.Contains(Constants.CertificateNotWithinValidityPeriod, StringComparison.OrdinalIgnoreCase) ||
responseBody.Contains(Constants.CertificateWasRevoked, StringComparison.OrdinalIgnoreCase);
#else
return responseBody.Contains(Constants.InvalidKeyError) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems error prone, if we add a new error code we need to remember to put it in multiple places. Perhaps a dictionary should be created above and referred to?

Copy link
Member

@bgavrilMS bgavrilMS left a comment

Choose a reason for hiding this comment

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

I recommend fixing the infinite loop instead. Retrying the cert once on a generic error seems ok.

@jmprieur
Copy link
Collaborator

Not instead. In addition: We got customer complains for retrying for a client secret.

@bgavrilMS
Copy link
Member

@tlupes - is this change is compatible with the new credential? I am ok to sign off if this is true.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Certificate reload logic triggers on all invalid_client errors, not just cert-related issues (regression from PR #3430)

4 participants