The OAuth Consent Screen Can Now Hand Back Less Than You Asked For
Cloudflare shipped optional scopes on 20 August. The field is one line of client configuration; the consequence lands in code that never had a reason to check.
Cloudflare added a field called optional_scopes to third-party OAuth clients on
20 August. A developer marks some of the permissions a client requests as
optional, the user clears the ones they do not want at the authorization screen,
and the access token that comes back carries only what survived.
Framed as consent, this is straightforwardly good. Framed as engineering, it moves a class of failure from install time to call time, in code that has never had a reason to handle it.
What shipped
The client record gained one field. Cloudflare's page on creating an OAuth client
lists it alongside client_name, grant_types, redirect_uris, scopes,
response_types, token_endpoint_auth_method and the rest, all managed at
https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/oauth_clients. The
example in the announcement leaves user-details.read and workers-scripts.write
required while putting workers-kv-storage.write and zone.read in
optional_scopes.
Nothing changes for an existing integration until someone populates that field. This is opt-in per client rather than a platform-wide behaviour change, which is the first thing a technical lead needs to know, and the announcement is clear about it.
What the consent screen now does
The consent screen separates the requested permissions into required and optional, and lets the user clear the optional ones. Cloudflare then issues a token containing only the scopes that were consented to.
One detail there deserves a second read. Required and optional are evaluated against the scopes requested in that particular authorization flow, not against everything the client record lists. A client configured with eight scopes that asks for three is judged on those three. That is the right behaviour, and it means the client record is a ceiling rather than a description of any token issued under it.
The constraint the announcement leaves out
The announcement shows optional_scopes in a configuration snippet without
stating any constraint on its contents. The documentation states one:
optional_scopes is a subset of scopes. A permission the client does not
request cannot be marked optional.
That reads like a formality, and it is — until the day someone trims the client's scope list. The two fields are defined in terms of each other, so they have to be edited together. That is a small thing to put in a runbook now and an irritating thing to discover from a rejected configuration change later.
Which field tells you what you actually got
The announcement does tell developers to check the granted scope set after exchanging the authorization code, rather than assuming the full requested set was approved. It does not say which field carries that set. The client-creation documentation does not either — it covers the request side and stops.
RFC 6749 answers it. Section 3.3 requires that an authorization server issuing a
token whose scope differs from the requested scope MUST include a scope
response parameter telling the client what was actually granted, and the
parameter appears in the access token response definitions in sections 4.1.4,
4.2.2, 4.3.3 and 4.4.3. This has been in the specification since OAuth 2.0 was
published. What is new is that the case it covers, a grant narrower than the
request, stops being an edge case and becomes an ordinary Tuesday.
So the answer to the question the announcement raises is a field that has been sitting next to the access token all along, in the part of the response body a client has never had a reason to read.
The claim, and what would show it wrong
Once a client populates optional_scopes, its own configuration stops being a
valid description of what its tokens can do, and the scope parameter in the
token endpoint response becomes the only in-band source of truth.
What would show that wrong: Cloudflare's token endpoint omitting scope when it
narrows a grant. I could not find a Cloudflare page documenting the token
response fields — the client-creation page covers the request side only — so this
is the single thing here not confirmed against a primary source, and it is
precisely the thing to confirm before you ship. If the field is absent, there is
no in-band channel at all and the fallback is the bad one: learning about a
missing permission from an API call that fails in front of a user.
This matters more than it used to because of what is doing the asking. If your agent requests, through third-party OAuth, the union of every permission it might ever need, those speculative scopes are the ones a user is most likely to clear — and the ones your code is least likely to have a path for, because they were never needed on the day you wrote it. Enterprise AI work fails at the last mile far more often than at the model, and this is a new, small piece of last mile — the same shape of problem as everything in why enterprise AI doesn't ship, arriving in the auth layer.
What to do this quarter
Three things, cheapest first.
Log the entire token endpoint response body once, in a throwaway account, and look at what is next to the access token rather than at the token itself:
jq -r '.scope' token-response.json
A null there is the finding, not a mistake in the command: it means there is no in-band grant report and you will have to probe the API instead.
Then make the granted set the thing your code branches on. Store it next to the token, and gate each feature on the scope it needs at the point of use, not at login. If your integration currently has a single boolean for connected, that boolean is now wrong.
Then decide, deliberately, which of your scopes can survive being declined. A
scope belongs in optional_scopes only if there is a real product behaviour for
its absence — a hidden panel, a disabled action, a clear explanation. Anything
else stays required, and the honest number of optional scopes for most
integrations on day one is zero. Shipping the field before shipping the
degradation paths is how a consent improvement becomes a support ticket, which
is the same trap that catches teams
moving a proof of concept into production.
Turn this into a plan for your team.
One week, fixed fee: a working session with your team, a prioritized use-case backlog, and an ROI model for the opportunities worth chasing.