Skip to content

Don't cache tokens rejected by the auth code nonce or max_age check - #979

Open
rumaisaaaaa263 wants to merge 1 commit into
AzureAD:devfrom
rumaisaaaaa263:oidc-validate-before-cache
Open

rumaisaaaaa263 wants to merge 1 commit into
AzureAD:devfrom
rumaisaaaaa263:oidc-validate-before-cache

Conversation

@rumaisaaaaa263

Copy link
Copy Markdown

Tokens rejected by the nonce or max_age check are still cached

oidc.Client.obtain_token_by_auth_code_flow() checks the ID token nonce and auth_time after the redemption call returns, but by then oauth2.Client._obtain_token() has already handed the response to on_obtaining_tokens, which ClientApplication wires to token_cache.add(). So when acquire_token_by_auth_code_flow() or acquire_token_interactive() raises the nonce or max_age RuntimeError, get_accounts() already lists the account and acquire_token_silent() returns the rejected access token from cache. That undoes the nonce protection against an injected authorization code and the max_age freshness requirement, and an app that persists its cache keeps those tokens after the failed sign-in. acquire_token_by_authorization_code(nonce=...) has the same ordering.

The token event is now held until the checks pass and then passed to the real callback, so a valid response is cached the same way as before. TestAuthCodeTokensRejectedByIdTokenChecksAreNotCached drives the public API with a mismatched nonce, a stale auth_time under max_age=60, and the legacy nonce= path; those three tests fail on dev and pass with this change, and a fourth checks that a response passing both checks is still cached and served by acquire_token_silent(). The unit test command from .Pipelines/template-pipeline-stages.yml passes on Python 3.9 (459 passed, 20 skipped).

obtain_token_by_auth_code_flow() and obtain_token_by_authorization_code()
checked the ID token nonce and auth_time only after the token response
had already gone through on_obtaining_tokens. Tokens from a rejected
response therefore stayed in the token cache, and acquire_token_silent()
would hand them out. Hold the event until the checks pass.
Copilot AI balanced review requested due to automatic review settings October 30, 2026 06:48
@rumaisaaaaa263
rumaisaaaaa263 requested a review from a team as a code owner October 30, 2026 06:48

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.

🟡 Changes recommended

The rejection tests do not check the ID-token cache, so they could miss a regression that stores only that credential.

1 open issue
What changed in this PR

This PR prevents rejected authorization-code tokens from entering MSAL’s cache by delaying token callbacks until nonce and max_age validation succeeds.

Changes:

  • Defer token callbacks in both current and legacy authorization-code flows.
  • Add regression tests for rejected responses and verify valid tokens remain available through silent acquisition.
File Description
tests/​test_application.py Adds cache-rejection tests and a successful caching test.
msal/​oauth2cli/​oidc.py Holds token events until ID-token checks pass.

Review effort: Balanced

Comment thread tests/test_application.py
self.assertEqual([], app.get_accounts())
for credential_type in (
msal.TokenCache.CredentialType.ACCESS_TOKEN,
msal.TokenCache.CredentialType.REFRESH_TOKEN):

This branch has not been deployed

No deployments
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.

2 participants