Skip to content

Answer the claims challenge on activation - #8

Merged
winebarrel merged 2 commits into
mainfrom
claims-challenge
Aug 31, 2026
Merged

winebarrel merged 2 commits into
mainfrom
claims-challenge

Conversation

@winebarrel

@winebarrel winebarrel commented Aug 31, 2026 •

Copy link
Copy Markdown
Owner

Problem

A role whose PIM policy sits behind a Conditional Access authentication context
is refused at activation time: Graph answers with status 400 and
RoleAssignmentRequestAcrsValidationFailed, and buries a claims challenge in
the message as a URL-encoded query fragment (&claims=...) naming the context
it wants.

The challenge asks for a token carrying the acrs claim, which is only issued
when the sign-in itself asked for the context. azpim discarded it, so such a
role could not be activated at all — signing in again produced the same token
and the same refusal.

Change

  • Error.ClaimsChallenge() digs the challenge out of the message it is buried
    in, and only when it decodes to an actual claims document, so a message that
    merely mentions the word does not open a browser.
  • Client.do answers a challenge by re-acquiring the token and sending the same
    request once more. It lives there because any PIM call can be challenged and
    the retry has to be the request that was refused, not one rebuilt from
    scratch. A second refusal is returned as it came back.
  • Authenticator.TokenWithClaims passes the claims to the authorization
    request. A refresh cannot produce the acrs claim, so a challenge goes
    straight to the browser instead of trying silently first.
  • Challenged tokens are cached under their own key, so one is not handed to a
    plain call and the plain one is not handed back to the challenge that just
    refused it. A second activation within the token's life opens no browser.

The claims are never sent unprompted: which contexts exist and which roles
require them is a per-tenant setting, so asking for one would fail in a tenant
that has not defined it and prompt for nothing in one that has. The value is
echoed back as the tenant wrote it rather than hardcoded.

Tests

Covers the retry and its token swap, an unchanged retry body, a challenge with
no way to answer it, one still refused afterwards, an abandoned sign-in, a
message that only mentions claims, the claims reaching the authorize request,
an ordinary sign-in not carrying them, the separate cache entry, and the
skipped refresh.

A role can sit behind a Conditional Access authentication context, which PIM
enforces at the moment of activation rather than at sign-in. The refusal
carries a claims challenge, and it was being discarded, so such a role could
not be activated at all however many times you signed in again.

The challenge is now dug out of the message it is buried in, answered with a
sign-in carrying those claims, and the request that was refused is sent once
more, unchanged. A second refusal is reported as it came back, so a context
this tool cannot satisfy is not disguised as the original failure.

The claims are never sent unprompted. Which authentication contexts exist and
which roles require them is a per-tenant setting, so asking for one would fail
in a tenant that has not defined it and prompt for nothing in one that has. A
token issued for a challenge is cached apart from the ordinary one, since
neither can stand in for the other, and a refresh is skipped when answering a
challenge because it cannot produce the acrs claim being asked for.
@codecov

codecov Bot commented Aug 31, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.98%. Comparing base (e8c1d6c) to head (353aeaa).

Additional details and impacted files
@@            Coverage Diff             @@
##             main       #8      +/-   ##
==========================================
+ Coverage   96.57%   96.98%   +0.41%     
==========================================
  Files           7        7              
  Lines         496      531      +35     
==========================================
+ Hits          479      515      +36     
+ Misses         17       16       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The retry brought in a handful of branches nothing exercised: the way a client
reaches back to the authenticator, a retry that cannot reach Graph, and a reply
that stops partway through, which had gone untested while it lived inline.
@winebarrel
winebarrel enabled auto-merge August 31, 2026 02:51
@winebarrel
winebarrel merged commit 45bd164 into main Aug 31, 2026
4 checks passed
@winebarrel
winebarrel deleted the claims-challenge branch August 31, 2026 02:52
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.

1 participant