Let a role behind an authentication context actually activate - #197
Merged
Merged
Conversation
A role whose policy carries AuthenticationContext_EndUser_Assignment could not be activated at all: the request was refused, the browser step-up was completed, and the retry was refused again with the same message, however many times it was repeated. Two faults on the path between the step-up and the retry, and both had to go. Elevate never declared the cp1 client capability, so its tokens carried no xms_cc claim. PIM will not honour an authentication context from a client that has not said it understands a claims challenge: it answers with RoleAssignmentRequestAcrsValidationFailed and re-issues the same challenge, even for a token that plainly carries the context — confirmed against a live tenant, where a token with acrs=c10, amr=fido,rsa,mfa and the right scopes was refused until cp1 was declared. Elevate has always handled claims challenges, so it now says so: through MSAL's client configuration on macOS, Windows and the CLI, and as an xms_cc claims request on every token the loopback providers mint themselves, refreshes included, since a refreshed token would otherwise lose it. And the token the step-up produced was thrown away, the retry asking MSAL silently for "the" token for those scopes instead. MSAL bypasses its access-token cache whenever a claims request is specified and makes no promise to write the result back into it, so the retry could go out with the token from before the step-up. The same live trace showed it plainly: the held token carried acrs=c10, MSAL's silent call for the same account and scopes returned one without it. StepUpTokenCache holds the step-up token for its own lifetime and serves it to the retry, which also spares the second and third role behind the same context their own browser round trip. A forced refresh still goes past it, as it goes past MSAL's cache, so the propagation probes are unchanged. Verified against a live tenant on Windows: the activation that had been refused indefinitely now succeeds. NOT COMPILED on macOS. Written on a Windows machine with no Swift toolchain, so 'swift test' has never run against any of it. The Windows and CLI suites pass (965 tests) and the Windows app was built and driven by hand; the macOS half mirrors it and needs a run on a Mac. The loopback change there — the capability on every token request — is the one part no test exercises against a real server. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
FrodeHus
enabled auto-merge (squash)
September 19, 2026 09:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A role whose PIM policy carries
AuthenticationContext_EndUser_Assignmentcould not be activated at all. The request was refused, the browser step-up was completed, and the retry was refused again with the same message — "This role requires the Conditional Access authentication context … and the sign-in did not satisfy it" — however many times it was repeated.Two faults on the path between the step-up and the retry, and both had to go.
The client never said it could handle a claims challenge
Elevate did not declare the
cp1client capability, so its tokens carried noxms_ccclaim. PIM will not honour an authentication context from a client that has not said it understands a claims challenge: it answers withRoleAssignmentRequestAcrsValidationFailedand re-issues the same challenge, even for a token that plainly carries the context.Confirmed against a live tenant. The token being refused had everything the policy asked for:
— and no
xms_cc. Elevate has always handled claims challenges, so it now says so: through MSAL's client configuration on macOS, Windows and the CLI, and as anxms_ccclaims request on every token the loopback providers mint themselves, refreshes included, since a refreshed token would otherwise lose it.The step-up token was thrown away
The retry asked MSAL silently for "the" token for those scopes. MSAL bypasses its access-token cache whenever a claims request is specified and makes no promise to write the result back into it, so the retry could go out with the token from before the step-up. The same live trace showed it plainly — the held token carried
acrs=c10, MSAL's silent call for the same account and scopes returned one without it:StepUpTokenCacheholds the step-up token for its own lifetime and serves it to the retry, which also spares the second and third role behind the same context their own browser round trip. A forced refresh still goes past it, as it goes past MSAL's cache, so the propagation probes are unchanged.Verification
swift buildandswift testare clean: 413 ElevateCore tests pass. The Xcode target — which is the only thing that compilesMSALTokenProvider.swift, thecp1capability and the step-up cache wiring — builds and its app tests pass (xcodebuild … build/… test, Swift 6.4 / Xcode 26). No new warnings: the two inMSALTokenProvider.swiftsit on the pre-existingapp.signoutandapp.acquireTokencallbacks.StepUpTokenCachematches the C# one field for field, including the 60s skew and the 5-minute opaque-token lifetime.Worth a reviewer's judgment
cp1also declares CAE-readiness, so Graph may begin issuing long-lived CAE tokens and sending 401 claims challenges. Elevate already parses those and re-acquires against them, which is why declaring it is accurate rather than a workaround — but it is a real behavioural change beyond the PIM path.🤖 Generated with Claude Code