feat(oauth): add MCP / RFC 8707 + RFC 8414 compatibility#187
Merged
Conversation
- Add RFC 8707 Resource Indicators across authorization_code, device_code, refresh_token, and client_credentials grants - Bind issued JWT aud to the requested resource and persist resource on auth codes and access/refresh token rows - Enforce subset rule on refresh and token exchange per RFC 8707 §2.2 - Add /.well-known/oauth-authorization-server endpoint (RFC 8414) with curated OAuth-only metadata - Apply CORS middleware to /.well-known/* for browser-based MCP clients - Reject non-http(s) schemes and cap resource-list size in the validator - Add docs/MCP.md integration guide Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds MCP-oriented OAuth compatibility by supporting RFC 8707 resource indicators, publishing OAuth AS metadata, and enabling CORS for well-known discovery endpoints.
Changes:
- Threads
resourceindicators through authorization, token issuance, refresh, and JWT audience generation. - Adds OAuth AS metadata and well-known CORS coverage.
- Adds persistence fields, tests, mocks, and MCP integration documentation.
Reviewed changes
Copilot reviewed 37 out of 38 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
internal/util/slice.go |
Adds string-slice subset helper for resource narrowing checks. |
internal/util/slice_test.go |
Covers subset helper behavior. |
internal/util/resource.go |
Adds resource indicator validation. |
internal/util/resource_test.go |
Covers resource validation cases. |
internal/token/local.go |
Adds audience override support to JWT generation and refresh. |
internal/token/local_test.go |
Updates token provider call sites for audience parameter. |
internal/token/local_extra_claims_test.go |
Updates extra-claims tests for new provider signature. |
internal/templates/props.go |
Adds resource values to authorize page props. |
internal/templates/authorize.templ |
Preserves resources through consent POST. |
internal/services/token.go |
Persists resource values on issued token pairs. |
internal/services/token_uid_test.go |
Updates service tests for resource parameter. |
internal/services/token_test.go |
Updates existing token tests for resource-aware signatures. |
internal/services/token_resource_test.go |
Adds resource/audience integration tests. |
internal/services/token_refresh.go |
Enforces refresh-time resource subset checks. |
internal/services/token_profile_test.go |
Updates token profile test call sites. |
internal/services/token_private_claim_prefix_test.go |
Updates private-claim tests for new signatures. |
internal/services/token_introspect_test.go |
Updates client credentials issuance call site. |
internal/services/token_exchange.go |
Threads resource through auth-code and device-code exchanges. |
internal/services/token_domain_test.go |
Updates domain-claim tests for resource parameter. |
internal/services/token_client_credentials.go |
Threads resource into client credentials access tokens. |
internal/services/token_client_credentials_test.go |
Updates client credentials tests for resource parameter. |
internal/services/token_cache_test.go |
Updates cache tests for new token provider signature. |
internal/services/token_cache_bench_test.go |
Updates benchmark token generation call. |
internal/services/authorization.go |
Persists authorize-time resources on authorization codes. |
internal/services/authorization_test.go |
Updates authorization request validation tests. |
internal/models/token.go |
Adds persisted token resource field. |
internal/models/authorization_code.go |
Adds persisted authorization-code resource field. |
internal/mocks/mock_token.go |
Regenerates token provider mock signatures. |
internal/handlers/token.go |
Parses resource parameters and applies grant-specific behavior. |
internal/handlers/token_introspect_test.go |
Updates token generation call site. |
internal/handlers/oidc.go |
Adds OAuth AS metadata and shared discovery metadata construction. |
internal/handlers/oidc_test.go |
Adds OAuth metadata and OIDC regression tests. |
internal/handlers/authorization.go |
Validates and preserves authorize-time resource indicators. |
internal/handlers/authorization_test.go |
Adds invalid-target mapping and authorize rejection test. |
internal/core/token.go |
Extends token provider interface with audience parameter. |
internal/bootstrap/wellknown_cors_test.go |
Adds well-known CORS tests. |
internal/bootstrap/router.go |
Groups well-known endpoints and applies CORS when enabled. |
docs/MCP.md |
Documents MCP integration and resource indicator behavior. |
Files not reviewed (1)
- internal/mocks/mock_token.go: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Cap per-resource URI at 1024 chars and require non-empty host for http(s) values - Move RFC 8707 §2.2 subset check into ExchangeCode so a rejected resource does not burn the single-use authorization code - Preserve the rotated refresh token's audience as the original grant so narrowing once does not permanently shrink the refresh token's resource - Restrict introspection endpoint auth methods to exclude `none` since /oauth/introspect rejects unauthenticated requests - Advertise device_authorization_endpoint and resource_indicators_supported in the OAuth AS metadata - Register OPTIONS handlers on /.well-known/* so browser CORS preflights reach the CORS middleware instead of 405-ing first - Update docs/MCP.md preflight example to use OPTIONS with Access-Control-Request-Method Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Reorder /authorize validation so redirect_uri is proven registered before the resource parameter is parsed, closing an open-redirect window where an invalid resource on an unregistered redirect_uri could be reflected - Apply strict RFC 8707 §2.2 subset rule at /token: reject any token-time resource when /authorize bound none, mirroring the refresh-grant rule - Preserve the full /authorize-time grant on the refresh token issued via authorization_code so future refreshes can re-narrow against the original audience rather than the access token's narrowed set - Include `aud` in the RFC 7662 introspection response so resource servers that authorize via introspection can enforce audience binding - Skip ConsentRemember when the request includes resource indicators, so a user must explicitly approve each new audience their tokens bind to - Render requested resources visibly on the consent page next to scopes - Emit `resource_parameter_supported` alongside `resource_indicators_supported` in the OAuth AS metadata for both naming conventions in the wild - Add a client_credentials + resource integration test Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 37 out of 38 changed files in this pull request and generated 9 comments.
Files not reviewed (1)
- internal/mocks/mock_token.go: Language not supported
Comments suppressed due to low confidence (1)
internal/handlers/token.go:440
- This emits
audfor every active token row, including refresh tokens whoseResourceis now persisted. Since the introspection response still reports refresh tokens as active withtoken_type: "Bearer"and does not expose the access/refresh category, a resource server that relies on RFC 7662 plusaudcannot distinguish a refresh token from an access token. Restrict this audience-bearing response to access tokens or include/enforce the token category so refresh tokens are not accepted as resource-server credentials.
// Audience binding: prefer the RFC 8707 resource set persisted at
// issuance; fall back to the static JWTAudience config when no resource
// was requested. Resource servers rely on this to enforce that a token
// minted for service A cannot be replayed against service B.
if aud := introspectAudience(tok, h.config.JWTAudience); aud != nil {
resp["aud"] = aud
…ariants - Render an error page (not a redirect) when ValidateAuthorizationRequest fails with ErrInvalidRedirectURI or ErrUnauthorizedClient, per RFC 6749 §3.1.2.4. Closes the open-redirect path where an unregistered redirect_uri was still reflected as the OAuth error redirect target - Split provider RefreshAccessToken into accessAudience and refreshAudience; refresh tokens no longer carry a resource-server `aud` claim (they go to the AS, not the RS, so emitting an RS audience risked confusing JWT validators that only check signature/iss/exp/aud) - Eliminate the wasted second access-token mint in token rotation: with the two-audience provider signature the access token is issued with the narrowed audience and the refresh token with no resource audience in a single round-trip - Add a service-boundary RFC 8707 §2.2 subset check in ExchangeAuthorizationCode so any future call path bypassing the handler still enforces the audience invariant - Drop the JWTAudience config fallback in the introspection response so rotating JWT_AUDIENCE cannot diverge introspect `aud` from the JWT - Document that resource servers MUST reject non-access tokens by checking the `type` claim, otherwise a refresh token could be mistaken for one - Add tests for ExchangeCode subset rule (allowed subset, rejected superset with code remaining unconsumed, rejected request against empty grant) - Add a handler-integration test asserting the open-redirect mitigation: invalid resource on an unregistered redirect_uri is NOT reflected Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
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.
Summary
Adds OAuth 2.1 / MCP authorization spec compatibility to AuthGate so it can act
as a drop-in authorization server for any Model Context Protocol
deployment. Three gaps closed: RFC 8707 Resource Indicators (audience binding),
RFC 8414
/.well-known/oauth-authorization-servermetadata, and CORS on the/.well-known/*group for browser-based MCP clients.No new env vars; behaviour is backward-compatible for OAuth clients that don't
supply
resource(JWT_AUDIENCEconfig remains the fallback). See theBreaking changes section below for source-level and operational concerns.
Breaking changes
End-user OAuth clients (CLIs, web/mobile apps using the documented HTTP
surface) are unaffected — every new parameter is optional and behaviour
without
resourceis unchanged. The following items only matter for forks,custom integrations, and operators with non-default deployments.
Source-level (forks / out-of-tree implementations)
core.TokenProviderinterface signature change (internal/core/token.go).Any out-of-tree implementation will fail to compile:
GenerateToken,GenerateRefreshToken,GenerateClientCredentialsTokeneach gain a trailing
audience []stringparameter. Passnilfor thepre-PR behaviour.
RefreshAccessTokensplits its single audience parameter intoaccessAudience, refreshAudience []string— keeping refresh JWTs fromcarrying a per-request RS
aud. Passnil, nilfor the pre-PRbehaviour.
ValidateRefreshToken(ctx, tokenString) (*TokenValidationResult, error).internal/mocks/mock_token.go; vendored copiesmust be refreshed.
audinsideextra_claimsis now unconditionally strippedat sign time. Callers that previously smuggled audience via
extra_claimsmust move to the new
resourceparameter.Database
Resource StringArrayJSON column onAccessToken,DeviceCode,AuthorizationCode, andUserAuthorization. GORMAutoMigrate handles the migration on startup; existing rows have empty
Resource. No data backfill required.AccessToken.Resourcehas dual semantics — for access-token rows itis the JWT
audsnapshot taken at issuance; for refresh-token rows it isthe original grant's resource set (NOT the refresh JWT's
aud). Anythingreading this column directly (custom analytics, external migrations) must
branch on
TokenCategory. See the field's docstring for the fullrationale.
Operational —
JWT_AUDIENCEsemanticsRefresh JWTs are signed with the static
JWT_AUDIENCEas theiraudclaim(unchanged from pre-PR). The PR explicitly tightens the constraint:
deployments MUST point
JWT_AUDIENCEat an AS-only value, or leave itunset. If a deployment currently uses
JWT_AUDIENCE=<resource-server-id>,change it before upgrading — otherwise a refresh JWT could be silently
accepted as an access token by a resource server that only verifies
signature/iss/exp/aud. Access tokens issued without a per-request
resourcewill then carry no
aud(and resource servers should reject tokens whoseauddoes not match their own identifier — RFC 8707 §2.2).AI Authorship
via a written plan, 10 Copilot review rounds, two
/simplifycycles, one
/security-reviewpass (no high-confidence findings),and a three-round documentation update covering 11 docs files. Each
review's findings were fixed in the same branch before this PR was marked
ready. Reviewers should still expect to read carefully — see "Reviewer
guide" below.
Change classification
and all four OAuth grant flows. Failure is system-wide.
Plan reference
/Users/mtk10671/.claude/plans/authgate-mcp-mutable-nebula.md— scope, allowedfiles, three required e2e tests, and done-definition all defined there.
Verification
internal/util/resource_test.go,internal/util/slice_test.gointernal/services/token_resource_test.go(coversall four grants end-to-end with resource binding, subset acceptance on
refresh, superset rejection, cascade-revoke linkage, and JWT-audience
snapshot fallback)
during review:
TestAuthCodeFlow_WithResource_PropagatesToAud(happy path)TestAuthorize_RejectsResourceWithFragment(RFC 8707 §2.1 validation)TestRefresh_RejectsResourceSupersetOfOriginal(RFC 8707 §2.2 widening)TestClientCredentials_WithResource_PropagatesToAudTestDeviceCode_WithResource_PropagatesToAudTestDeviceCode_RejectsResourceSupersetOfGrantTestDeviceCode_RejectsResourceWhenNoneGrantedTestDeviceCode_LinksAuthorizationIDForCascadeRevokeTestClientCredentials_NoResource_SnapshotsJWTAudienceTestRefresh_NarrowsResource_SubsetTestAuthorize_InvalidResource_RedirectsAfterValidationTestAuthorize_UnsupportedResponseType_UnregisteredRedirectURI_NotReflectedTestAuthorize_InvalidResource_UnregisteredRedirectURI_NotReflectedTestDeviceCodeRequest_WithResource_PersistsOnDeviceCodeTestDeviceCodeRequest_InvalidResource_ReturnsInvalidTargetTestIntrospectAudience/.well-known/openid-configurationshape cannot drift(
TestOIDCDiscovery_UnaffectedByOAuthMetadataAddition).well-knowngroup/.well-known/*responses are cached(Cache-Control: public, max-age=3600) and the token endpoint is unchanged
in hot-path shape
Tester / QA — Manual Verification Guide
Run through these steps to confirm the PR works end-to-end. Time: ~10–15 minutes. The minimal "smoke" subset is steps 1, 2, 4, 5, 7 (≈5 minutes); the rest exercise edge cases and security invariants.
Prerequisites & setup
On first start, admin credentials and an initial CLIENT_ID are written to
authgate-credentials.txt(mode 0600). Log in to http://localhost:8080 as admin and create a test OAuth client for the manual tests:http://localhost:9999/callbackread writeSave the generated
client_id— referenced below as$CID.Step 1: Automated test suite (30 seconds)
Expect: all tests PASS, including
internal/util/resource_test.go,internal/util/audience_test.go, andinternal/services/token_resource_test.go(16+ new tests covering all four grants with resource binding).Step 2: RFC 8414 — new AS Metadata endpoint
curl -s http://localhost:8080/.well-known/oauth-authorization-server | jqExpect to see
resource_indicators_supported: true,device_authorization_endpoint,code_challenge_methods_supported: ["S256"]. The existing OIDC discovery endpoint should still respond unchanged:curl -s http://localhost:8080/.well-known/openid-configuration | jq .issuerStep 3: CORS preflight on
/.well-known/*(for browser-based MCP clients)Restart the server with CORS enabled:
Step 4: Device Code Flow with resource binding (UI: DeviceConfirmPage)
verification_uri_completein a browser.https://api.example.comunder "Access will be granted to:" with a blue left-border chip and an arrow marker.Save
access_tokenandrefresh_tokenfor the next steps.Step 5: Verify the JWT
audclaim (the core RFC 8707 invariant)Expect:
audclaimtypeclaim"https://api.example.com"(the per-request resource)"access""https://authgate.local"(the AS-onlyJWT_AUDIENCE, NOThttps://api.example.com)"refresh"If the refresh token's
audmatches the per-request resource, the audience-separation invariant is broken — fail the PR.Step 6: Authorization Code Flow with resource (UI: authorize consent page)
Build a PKCE-enabled authorize URL:
On the consent page, verify:
readwith a green ✓ circle<input type="hidden" name="resource">and confirm two hidden inputs with the resource valuesClick Allow Access and observe the redirect to
localhost:9999/callback?code=.... Then exchange the code:Decode the new access token —
audshould be["https://api.example.com", "https://mcp.example.com"].Step 7: RFC 8707 §2.2 narrowing rule (subset accept, widen reject)
Decode the new access token from the narrowing case —
audshould be only"https://api.example.com"(single string, not array).Step 8: Resource validation rejection (security-critical)
All of these should return
400 invalid_target:Step 9: Remembered consent strict matching
mcp.example.com, keepapi.example.com) → expect the consent screen to re-appear.This proves
IsStringSliceSetEqualis enforced — a previously-approved consent is never silently extended to cover a different audience set.Step 10: Cascade-revoke for device-code grants
/account/authorizations(logged in as the user from Step 4).<code>chips under "Resource(s):"/oauth/tokeninfo:invalid_token— before this PR, device-code tokens were orphaned from the consent row and revoke silently failed.Step 11: Database migration
If upgrading an existing deployment:
All four tables should now have a JSON
resourcecolumn. Existing rows have emptyresourceand continue to work (fall back toJWT_AUDIENCE).Optional: end-to-end with the demo CLI
Walks the device flow and confirms a real CLI client can complete the flow against this branch.
Pass criteria
The PR is verified once all of the following hold:
make test,make lintpass cleanlyresource_indicators_supported: trueaud= per-request resource; refresh tokenaud=JWT_AUDIENCE(separation invariant)audinvalid_targetinvalid_target/account/authorizationsinvalidates device-code tokensresourcecolumn to all four tables on existing DBsVerifiability check
docs/MCP.mdand inline godoccitations appear at every enforcement point
the existing OAuth error response path (counted by existing metrics)
Security check
ValidateResourceIndicatorsrejectsnon-http(s) schemes (blocks
javascript:/data:/file:audvalues),empty strings, relative URIs, fragments, and caps the list at 10 entries
to prevent DoS amplification
on
authorization_code→ token exchange, ondevice_code→ tokenexchange (against the device-time grant), and on
client_credentials(against
JWT_AUDIENCEsnapshot when noresourcesupplied); testedwith dedicated tests
/oauth/authorizeand thedevice-flow consent page both honour the same redirect-URI allowlist
check; invalid
response_type/resourceon an unregisteredredirect_uriare not reflected back/oauth/*unchangeddescriptions
audcannot be smuggled viaextra_claims— explicitdelete(claims, "aud")runs before the audience override applies (defense in depth)audis bound at issuance — snapshotted onto the access tokenand re-applied on refresh; refresh cannot widen
audbeyond the originalgrant
audis bound —/oauth/tokeninforeflects the per-tokenaudrather than the staticJWT_AUDIENCEconfigauthorization ID so admin revoke cleanly reaches all descendants
<input name="resource">rendering preventsreflected XSS
Risk & rollback
Risk —
core.TokenProviderinterface gained a trailingaudience []stringparameter on four methods (
GenerateToken,GenerateRefreshToken,GenerateClientCredentialsToken,RefreshAccessToken).LocalTokenProvideris the only in-tree implementer; any external implementation would break
at compile time. Mocks were updated in lockstep.
Risk — Four models gained nullable
resourcecolumns:AuthorizationCode— captures theresourcefrom/oauth/authorizeAccessToken— snapshots the issuedaudso refresh cannot widen itDeviceCode— captures theresourcefrom/oauth/device/codeUserAuthorization— per-resource consent grants (one record peruser+app+resource tuple, so granting two distinct resources to the same
app no longer collapses into a single consent record)
GORM
AutoMigrateadds these transparently on Postgres and SQLite; nobackfill required. Existing rows default to NULL/empty and fall back to
JWT_AUDIENCE.Risk —
internal/templates/device_confirm_page.templis new (device-flowconsent screen). Existing device-flow users will see one extra confirm step
on first authorization; subsequent uses re-use the stored
UserAuthorization.Rollback — reverting the PR returns
audto the static-config path.Existing tokens remain valid (the new columns default to NULL/empty, and
the audience source falls back to
JWT_AUDIENCE). The new templ templateis embedded — no asset migration needed.
Reviewer guide
internal/util/resource.go— RFC 8707 §2.1 validation (security-critical)internal/handlers/authorization.go— POST deny path's redirect-URIallowlist check (open-redirect closure)
internal/handlers/token.go:handleAuthorizationCodeGrant— subset rulebetween authorize-time and token-time resource
internal/services/token_refresh.go— RFC 8707 §2.2 subset on refresh,plus
audsnapshot re-applicationinternal/services/token_exchange.go— device-code → token resourcesubset check against the device-time grant
internal/services/device.go+internal/models/device_code.go— deviceresource persistence and consent-page rendering
internal/services/authorization.go+internal/models/user_authorization.go— per-resource consent grants (composite key changed)
internal/token/local.go:generateJWT—audsource precedence (overridevs config), explicit strip of caller-supplied
audinternal/handlers/oidc.go— newoauthASMetadatashape vsdiscoveryMetadata, plus introspectionaudfixservices/token_*.gofiles threadingresourceend-to-end(mechanical change; tests cover the wiring)
nilto call sitesinternal/mocks/mock_token.gointernal/templates/device_confirm_page.templrendering (templauto-escaping handles the only user-controlled field)
Post-review revisions
The branch went through multiple review phases after the initial implementation
commit. Commits are listed oldest → newest within each phase.
Copilot review rounds — audience invariants, open-redirect, race conditions
a4926d3— first round of Copilot findingsb765220— second round4e10389— open-redirect closed, refresh-audsnapshot, audience invariants66e61d0— device-code resource binding, introspectionaudfixeb92f3a—UserAuthorizationmade resource-aware (composite key change)b634503— device confirm page, cascade-revoke linkage, swagger/docse41d8d2— POST deny redirect, resource validation tighten, race fix,client-credentials snapshot
audbaf8cc4— deny PKCE on public clients, refreshaudsnapshot for grantswith no
resource, atomic device-consent transaction, tokeninfoaudemission0a591dc— in-transactiondc.IsExpired()re-check, introspectaudfallback,
unsupported_response_typeredirect path, DCR doc fix, revocationendpoint auth-methods metadata
9ab39e9— revert unsafe introspectaudlive-config fallback (snapshotpurity restored), use JWT-signed
audon refresh for legacy rows,handler-layer regression coverage for the device confirm page
Code simplification (
/simplifypass)77cb30b— consolidate three near-duplicate audience helpers intoutil.AudienceClaim+util.AudienceFromClaims; extractnarrowResourcefor the triplicated RFC 8707 §2.2 subset-and-fallback pattern across the
device-code, authorization-code, and refresh-token grants; convert
parseResourceParamto a free function; replace stringly-typed metriclabels with
models.TokenCategory*constantsUI polish — resource indicator visual treatment
The PR-added resource markup originally rendered with browser-default styles
(unstyled
<ul>bullets, untreated<code>) and the authorize page reusedthe green scope-permission check circle for resource arrows, creating a
semantic mismatch (audience target ≠ permission grant). Three commits fix this:
8edb5f8— style.device-resource-*/.authorization-resource-*/.device-cancel-linkon the device authorization page, device confirm page,and account authorizations list
581c0e7— replace reused green scope-check circle on the authorize consentpage with a dedicated bullseye SVG marker in a blue tinted circle, so
audience targets visually read as a different security category from
permission scopes
Documentation — three rounds covering 11 files
15e18d4(Round 1, critical surfaces) —README.mdadvertises MCP / RFC 8707/ RFC 8414;
docs/CONFIGURATION.mdadds theJWT_AUDIENCEoperationalconstraint and updated CORS scope;
docs/JWT_VERIFICATION.mdadds theAudience Binding (RFC 8707) section with
aud+typeclaimverification;
docs/MCP.mdcross-links andJWT_AUDIENCEcalloutce76227(Round 2, flow guides + security) —resourceparameter on allthree flow guides (
AUTHORIZATION_CODE_FLOW.md,DEVICE_CODE_FLOW.md,CLIENT_CREDENTIALS_FLOW.md) with examples; the M2M multi-resource-servercaveat;
docs/SECURITY.mdadds Token Replay Across RS and Refresh-as-AccessToken Confusion to the threat model and four new production-checklist items
62ae7d5(Round 3, depth) —docs/ARCHITECTURE.mddocuments the four newResourcecolumns with dual access-vs-refresh semantics plus thecore.TokenProviderinterface signature change;docs/USE_CASES.mdaddsthe MCP Server Authorization Server and Multi-Resource-Server
Audience Binding scenarios;
docs/TROUBLESHOOTING.mdadds three newentries —
invalid_targetdebug table (11 root causes), refresh-as-accesstoken-confusion mitigation, consent re-prompt on resource-set mismatch
9793298,4d080d2— markdown formatter normalizationsSecurity review
A
/security-reviewpass against the post-revision branch found nohigh-confidence security vulnerabilities newly introduced by this PR. The
prior 10 Copilot rounds had already closed the obvious attack vectors
(open-redirect, refresh-as-access token confusion, audience binding leaks,
consent re-prompt strict matching, snapshot purity for introspection). Full
report in the review conversation.
Net delta vs
mainRound 3 docs landed)
(16+ new tests), the device confirm template, the audience-helper
consolidation, and the three-round documentation update