Skip to content

fix(identity): accept Entra's space-delimited scp string claim (Copilot SystemError) - #244

Merged
jrosskopf merged 1 commit into
mainfrom
entra-scp-string-claim
Aug 30, 2026
Merged

fix(identity): accept Entra's space-delimited scp string claim (Copilot SystemError)#244
jrosskopf merged 1 commit into
mainfrom
entra-scp-string-claim

Conversation

@jrosskopf

@jrosskopf jrosskopf commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

The actual Copilot Studio SystemError

With the card-path fix (#243) deployed, Copilot Studio's runtime finally reaches the agent: POST /a2a from CopilotStudio PowerFx/1.99.0 hit the pod (2026-08-30). It was rejected 401 with:

auth: JWT verification failed: JSON error:
invalid type: string "access_as_user", expected a sequence

Microsoft Entra packs the delegated-scope claim scp as a space-delimited string ("access_as_user User.Read"). The verifier deserialized scp only as a JSON array, so every Entra delegated token failed at deserialization — before any scope/audience logic — as a bare 401. Copilot surfaces that as an opaque SystemError with no detail, which is why it looked Microsoft-side.

Fix

scp now deserializes from either a string (whitespace-split, exactly the OAuth2 scope convention Entra follows) or an array. scopes() is unchanged; Google tokens (array/scope) are unaffected.

Test

scp_claim_accepts_both_entra_string_and_array_forms: string form, array form, scope-only fallback, and absent-all each deserialize and yield the right scopes. cargo test -p triton-identity → 6 passed; clippy clean.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Microsoft Entra packs the delegated-scope claim `scp` as a
space-delimited STRING (`"access_as_user User.Read"`), but the verifier
deserialized `scp` only as a JSON array. Every Entra delegated token
therefore failed verification with

    JWT verification failed: JSON error: invalid type: string
    "access_as_user", expected a sequence

i.e. a bare 401 — which Copilot Studio surfaced to the operator as an
opaque `SystemError` with nothing actionable. Confirmed live 2026-08-30:
`POST /a2a` from CopilotStudio PowerFx reached the pod and was rejected
with exactly this error_detail.

`scp` now deserializes from either a string (whitespace-split, the OAuth2
`scope` convention Entra follows) or an array. `scopes()` is unchanged.

Test: scp string form, array form, scope-only fallback, and absent all
deserialize and yield the right scopes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jrosskopf
jrosskopf merged commit 044866a into main Aug 30, 2026
3 checks passed
@jrosskopf
jrosskopf deleted the entra-scp-string-claim branch August 30, 2026 16: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