Conversation
11d7b49 to
7194f1e
Compare
|
|
||
| effective_scope = resolve_scope(scope: scope, prm: prm) | ||
| effective_scope = normalize_offline_access_scope(effective_scope, as_metadata: as_metadata) | ||
| effective_scope = select_scope(effective_scope, as_metadata: as_metadata) |
There was a problem hiding this comment.
One design question before the detailed review.
scope_selector receives the final list, so it cannot tell a challenged scope from a default one. On a 403 step-up, an allowlist selector that leaves out the challenged scope makes the flow re-authorize for what is already granted, and the retry fails with 403 again. Without a callback_handler, each such request ends in AuthorizationPendingError, so the user is redirected in a loop.
The spec treats the two sources differently: challenged scopes are authoritative for the operation, while scopes_supported is a default. I would like the selector to cover only that default:
- Called only when the challenge names no scope, with PRM
scopes_supported. It returns a subset, and[]requests none of them. - Challenged scopes skip it.
authorization_request_validatoralready accepts or refuses them. offline_accessstays controlled bygrant_types. The step-up union and the endpoint URL's query are left as they are.
That keeps each hook to one job, and a narrow contract can be widened after release while a wide one cannot be narrowed.
It does not let a host narrow the scopes of a 401 challenge, or drop offline_access through the hook. If your use case needs either of those, could you describe it? If I'm misunderstanding anything here, please feel free to correct me.
Why
The MCP 2026-07-28 scope-selection strategy recommends the challenge's
scope, then PRMscopes_supported(intended as a minimal basic set). The Ruby SDK follows that default. Some hosted clients need a narrower per-user policy, including a first authorization request with no client-providedscopeparameter;authorization_request_validatorcan refuse but cannot change the selected scopes.Fixes #578.
What changes
Add optional
Provider.new(scope_selector:)for authorization-code clients. It receives a read-only array after the SDK's normal selection andoffline_accessaugmentation, and returns valid replacement scope tokens ornil/[]to omit the URL'sscopeparameter. When configured, the selector also overrides prefilledscopeparameters in the authorization endpoint query, so validation and the browser URL agree; no-selector behavior is preserved. It runs before authorization-request validation and client registration. Unsupportedoffline_accessremains stripped. Filtering challenged scopes may leave the operation unauthorized; omittingscopedoes not prevent the AS from applying defaults (RFC 6749 §3.3).With no selector, the SDK's spec-aligned default and other OAuth grants are unchanged. This follows the application-policy hook pattern in the C# SDK and Go SDK.
Scope selection, illustrated
These examples assume no challenged scope, a Protected Resource Metadata (PRM) document advertising
read write admin, and an authorization server (AS) supportingoffline_accesswith a client declaring therefresh_tokengrant. The authorization-request validator shown here is optional.Default versus application policy
The hook changes what the client requests, not what the AS grants. Returning
nilor[]omits the client’sscopeparameter; it does not prevent AS defaults.Placement in a hosted OAuth flow
This uses the existing two-leg OAuth flow from PR 573. The scope selector is the addition here; callback persistence and
Flow#finish!are existing behavior. The host must inspect the actual granted scopes. Removing prefilled endpoint scopes when a selector is configured keeps the browser request consistent with the validator’s decision.Validation
git diff --checkpass.puma/native extensions; upstream CI validates lint.