Name the permissions a token holds when Graph answers 403 - #83
Open
aberoham wants to merge 2 commits into
Open
Conversation
Graph refused the presence writes with `Forbidden` and an empty `message`, so the failure said nothing about which permission was missing and looked like any other authorization refusal. Diagnosing it meant going and reading the token's scopes by hand. The CLI already holds them. A 403 now lists the delegated scopes the token carries, or its application roles when the token is app-only, and says what would add the missing one. The remedy differs by token type and by tenant: a delegated scope can be consented interactively, or through `auth consent-url` where the tenant reserves consent to an administrator; an application role is granted on the app registration and consented by an administrator, so no login can add one. An opaque token yields no claims and gets no hint rather than a guess. This follows the existing 401 audience hint, and covers all three places a 403 is turned into an error — `error_for_status`, `request_with_retry` and `request_with_retry_no_content` — each with a test that fails if its own call site is reverted. The app-only test also asserts the login advice is absent, so that branch cannot quietly regress to it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C736oc7CeNtRZmAJrXm8sG
The test named for `error_for_status` drove `get_paged`, which routes a single page through `request_with_retry` like any other GET, so it exercised a copy of the 403 handling that another test already covered and would have passed with `error_for_status` reverted. `error_for_status` is reached by `post_for_location` and the byte download, so the test now drives `get_bytes`. Reverting any one of the three call sites alone fails its own test and no other. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C736oc7CeNtRZmAJrXm8sG
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.
Graph refuses a request whose token lacks the necessary permission with 403
and, in the case that prompted this, an empty
message:{"error":{"code":"Forbidden","message":"", ...}}So the failure said nothing about what was missing and looked like any other
authorization refusal. Working it out meant running
teams auth statusandcomparing the scope list against the operation by hand.
The CLI already holds that list. A 403 now names the delegated scopes the
token carries, or its application roles when the token is app-only, and points
at
auth doctorandauth login:The remedy differs by token type and by tenant. A delegated scope can be
consented interactively, or through
auth consent-urlwhere the tenantreserves consent to an administrator, and the hint names both. An application
role is granted on the app registration and consented by an administrator, so
an app-only caller is told that rather than being pointed at
auth login,which cannot add one.
This follows
auth_error_message, which already appends an audience hint to a401, and covers all three places a 403 becomes an error:
error_for_status,request_with_retryandrequest_with_retry_no_content.The CLI cannot say which permission the operation needs without a mapping
from every endpoint to its scopes, which would be a table to maintain and get
wrong. Listing what the token has is the part it can know for certain.
An opaque token yields no claims and gets no hint rather than a guess.
One tradeoff worth naming: this puts the token's full permission list into an
error that may end up in a CI log or an agent transcript. It is not a
credential, and
auth statusalready prints the same list, but it is a wideraudience than before. Hiding it behind a verbose flag was the alternative and
would defeat the purpose, since the unattended caller is the one that cannot
go and look.
Tests
Five wiremock tests: a delegated token listing its scopes, an app-only token
listing its roles and getting the administrator remedy — asserted both by what
it says and by
teams auth loginbeing absent, so that branch cannot quietlyregress — an opaque token adding nothing, and one test per remaining call site,
a no-content write and a byte download. Reverting any one of the three call
sites alone fails its own test and no other — the byte download matters
because
error_for_statusis reached bypost_for_locationandget_bytes,not by a paginated read, which routes through
request_with_retrylike anyother GET.
Fixes #80
🤖 Generated with Claude Code
https://claude.ai/code/session_01C736oc7CeNtRZmAJrXm8sG