feat: add OAuth2 AS discovery endpoints, signing key management, and MCPServerAuthMode config#4505
feat: add OAuth2 AS discovery endpoints, signing key management, and MCPServerAuthMode config#4505Pratham-Mishra04 wants to merge 10 commits into
MCPServerAuthMode config#4505Conversation
|
|
📝 WalkthroughSummary by CodeRabbitRelease Notes
WalkthroughAdds MCP inbound OAuth2 authentication infrastructure: new ChangesMCP OAuth2 Discovery Infrastructure
Sequence Diagram(s)sequenceDiagram
participant Client
participant OAuth2DiscoveryHandler
participant ClientConfig
participant RDBConfigStore
participant GovernanceConfigDB as GovernanceConfig DB
Client->>OAuth2DiscoveryHandler: GET /.well-known/jwks.json
OAuth2DiscoveryHandler->>ClientConfig: IsMCPOAuthDiscoveryEnabled()
alt OAuth discovery disabled
OAuth2DiscoveryHandler-->>Client: 404 Not Found
else OAuth discovery enabled
OAuth2DiscoveryHandler->>RDBConfigStore: GetOAuth2SigningKey(ctx)
RDBConfigStore->>GovernanceConfigDB: SELECT by GovernanceConfigKeyOAuth2SigningKey
alt key exists
GovernanceConfigDB-->>RDBConfigStore: stored JSON
RDBConfigStore->>RDBConfigStore: unmarshal + Decrypt()
else key missing
RDBConfigStore->>RDBConfigStore: rsa.GenerateKey RSA-2048
RDBConfigStore->>RDBConfigStore: Encrypt() private key
RDBConfigStore->>GovernanceConfigDB: INSERT ON CONFLICT DO NOTHING
RDBConfigStore->>RDBConfigStore: use plaintext private PEM
end
RDBConfigStore-->>OAuth2DiscoveryHandler: OAuth2SigningKey
OAuth2DiscoveryHandler->>OAuth2DiscoveryHandler: parseRSAPublicKeyPEM
OAuth2DiscoveryHandler->>OAuth2DiscoveryHandler: rsaPublicKeyToJWK
OAuth2DiscoveryHandler-->>Client: 200 {"keys":[...]}
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" Comment |
Confidence Score: 5/5Safe to merge; all new functionality is gated behind the opt-in MCPServerAuthMode flag and does not affect existing deployments in their default headers mode. The key-generation race addressed by ON CONFLICT DO NOTHING is handled correctly. Encrypt/decrypt lifecycle mirrors existing hooks. The two remaining findings are both narrow quality issues on a code path that has no active callers yet (token issuance lands in a follow-up PR). transports/bifrost-http/handlers/oauth2_discovery.go (double lock / inconsistent PRM response) and transports/bifrost-http/handlers/config.go (missing TTL bounds check). Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client as MCP Client
participant WK as /.well-known/* Handler
participant Store as ConfigStore
participant DB as governance_config
Client->>WK: GET /.well-known/oauth-authorization-server
WK->>Store: ClientConfig.IsMCPOAuthDiscoveryEnabled()
alt "mode == headers"
WK-->>Client: 404 Not Found
else "mode == both | oauth"
WK->>Store: GetOAuth2SigningKey()
Store->>DB: "SELECT key WHERE key='oauth2_signing_key'"
alt key exists
DB-->>Store: encrypted JSON blob
Store-->>WK: OAuth2SigningKey (decrypted)
else first use
Store->>Store: rsa.GenerateKey(2048)
Store->>DB: INSERT ON CONFLICT DO NOTHING
alt insert won
Store-->>WK: OAuth2SigningKey (plaintext)
else another caller won
Store->>DB: SELECT reload
DB-->>Store: winner's key
Store-->>WK: OAuth2SigningKey (decrypted)
end
end
WK-->>Client: JSON discovery document / JWKS
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Client as MCP Client
participant WK as /.well-known/* Handler
participant Store as ConfigStore
participant DB as governance_config
Client->>WK: GET /.well-known/oauth-authorization-server
WK->>Store: ClientConfig.IsMCPOAuthDiscoveryEnabled()
alt "mode == headers"
WK-->>Client: 404 Not Found
else "mode == both | oauth"
WK->>Store: GetOAuth2SigningKey()
Store->>DB: "SELECT key WHERE key='oauth2_signing_key'"
alt key exists
DB-->>Store: encrypted JSON blob
Store-->>WK: OAuth2SigningKey (decrypted)
else first use
Store->>Store: rsa.GenerateKey(2048)
Store->>DB: INSERT ON CONFLICT DO NOTHING
alt insert won
Store-->>WK: OAuth2SigningKey (plaintext)
else another caller won
Store->>DB: SELECT reload
DB-->>Store: winner's key
Store-->>WK: OAuth2SigningKey (decrypted)
end
end
WK-->>Client: JSON discovery document / JWKS
end
Reviews (5): Last reviewed commit: "feat: adds mcp server oauth tables" | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (1)
transports/config.schema.json (1)
276-280: ⚡ Quick winDeclare the default for
mcp_server_auth_modein schema.The description documents
'headers'as the default, but the schema does not set"default": "headers". Add it so schema-driven tooling and generated config stay aligned with runtime expectations.As per coding guidelines, `transports/config.schema.json` is the source of truth for config fields.Suggested fix
"mcp_server_auth_mode": { "type": "string", "enum": ["headers", "both", "oauth"], + "default": "headers", "description": "How /mcp authenticates inbound MCP clients. 'headers' (default): VK/api-key/session headers only, discovery disabled. 'both': accepts header credentials and Bifrost-issued JWTs, discovery enabled. 'oauth': Bifrost JWTs only — WARNING: disables VK/header MCP access." },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@transports/config.schema.json` around lines 276 - 280, The mcp_server_auth_mode property in the schema documents 'headers' as the default in its description, but the schema object itself is missing the "default" field declaration. Add "default": "headers" to the mcp_server_auth_mode object definition to ensure schema-driven tooling and generated configurations are aligned with the documented runtime expectations.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@framework/configstore/clientconfig.go`:
- Around line 101-102: The GenerateClientConfigHash function does not include
the newly added MCPServerAuthMode and OAuth2ServerConfig fields in its hash
calculation, which means configuration changes to OAuth2 authentication settings
will not trigger reconciliation. Update the GenerateClientConfigHash function to
include both the MCPServerAuthMode field and the OAuth2ServerConfig field in the
hash computation to ensure config drift is properly detected.
In `@framework/configstore/migrations.go`:
- Around line 10905-10924: The migration with ID migrationName only defines the
Migrate function but lacks explicit rollback behavior. Add a Rollback function
to the migration object that removes the two columns (MCPServerAuthMode and
OAuth2ServerConfigJSON) from tables.TableClientConfig using the migrator's
DropColumn method, or if the migration is non-rollbackable, explicitly return an
error indicating that downgrade is not supported so operators have clear
expectations.
In `@framework/configstore/rdb.go`:
- Around line 6701-6707: The read path decrypts PrivateKeyPEM whenever
encrypt.IsEnabled() is true, but keys created before encryption was enabled are
stored as plaintext and will fail decryption, breaking OAuth operations. To fix
this, add an encryption status marker or version field to track whether each key
was encrypted when stored, then in the decrypt logic (around the
encrypt.IsEnabled() check in the code block starting at line 6701), only attempt
decryption if the encryption marker indicates the key was actually encrypted, or
detect plaintext keys and re-encrypt them on first read when encryption becomes
enabled. Apply the same logic to all similar decrypt paths mentioned in the
comment (also applies to lines 6731-6743).
- Around line 6688-6710: The current lazy signing-key creation has a race
condition where multiple concurrent callers can both miss the row, each
generating and saving different RSA keys, leading to inconsistency between
signed tokens and advertised keys. Fix this by making the creation atomic in the
createOAuth2SigningKey method: use an insert-on-conflict clause with DoNothing
set to true and Columns set to the key field, then check the RowsAffected count.
If RowsAffected is 0 (indicating a conflict where another process inserted
first), reload and decrypt the existing row before returning instead of
returning the newly generated key. Apply this fix to both the
createOAuth2SigningKey call path within GetOAuth2SigningKey and the other
similar signing-key creation location mentioned in the comment range.
In `@framework/configstore/tables/oauth2_server.go`:
- Around line 53-55: The comment for AccessTokenTTL in the OAuth2ServerConfig
incorrectly states the default value as 900 seconds, but the actual
DefaultAccessTokenTTL constant and schema both define it as 600 seconds. Update
the comment for AccessTokenTTL to correct the default value from 900 to 600
seconds to match the actual implementation and prevent confusion during
operational troubleshooting.
In `@transports/bifrost-http/handlers/config.go`:
- Around line 503-504: The assignments to updatedConfig.MCPServerAuthMode and
updatedConfig.OAuth2ServerConfig always overwrite existing values with zero
values when those fields are omitted from the payload during partial updates.
Instead of unconditionally assigning these fields, add conditional checks to
only update updatedConfig.MCPServerAuthMode and updatedConfig.OAuth2ServerConfig
when the corresponding payload.ClientConfig fields are actually provided (not
empty or zero values). This preserves existing runtime and database state when
these OAuth settings are not included in the update request.
- Around line 503-504: The MCPServerAuthMode and OAuth2ServerConfig fields in
the config handler are being assigned directly from the payload without
validating against the schema constraints defined in
transports/config.schema.json. Add validation logic before the assignments to
ensure MCPServerAuthMode conforms to the allowed enum values (headers, both,
oauth) and that OAuth2ServerConfig has the correct shape and applicability
according to the schema. If validation fails, return an appropriate error
response instead of persisting invalid configuration.
In `@transports/bifrost-http/handlers/plugins.go`:
- Around line 589-591: The isEnvVarObject function at line 589 is too permissive
in detecting EnvVar objects, treating any map containing the keys value,
env_var, and from_env as an EnvVar placeholder, even if additional fields are
present. This causes the function to incorrectly return the existing
configuration at line 591, discarding user updates. Tighten the isEnvVarObject
detection logic to only return true for objects that are exclusively EnvVar
objects with only the expected fields and no additional properties. Apply this
same fix to the similar check at lines 633-637 to prevent the same config
rollback issue from occurring in both locations.
In `@transports/bifrost-http/server/server.go`:
- Line 1397: The OAuth2 discovery routes registered by NewOAuth2DiscoveryHandler
are being blocked by APIMiddleware because the `/.well-known/` endpoint prefixes
are not whitelisted. Requests to these endpoints are rejected before reaching
the handler's discoveryEnabled() check. Add "/.well-known/" as a new entry to
the whitelistedPrefixes list in handlers/middlewares.go to allow these endpoints
through the APIMiddleware while letting the individual discovery handlers
continue to control access through their own discoveryEnabled() validation
logic.
In `@transports/config.schema.json`:
- Around line 299-306: The schema definitions for auth_code_ttl and
access_token_ttl fields currently only specify type as integer without enforcing
positive values, allowing zero or negative TTL values that would create invalid
credentials. Add a minimum constraint property to both auth_code_ttl and
access_token_ttl field definitions in the JSON schema to enforce that values
must be greater than zero, ensuring only valid positive integer values are
accepted.
---
Nitpick comments:
In `@transports/config.schema.json`:
- Around line 276-280: The mcp_server_auth_mode property in the schema documents
'headers' as the default in its description, but the schema object itself is
missing the "default" field declaration. Add "default": "headers" to the
mcp_server_auth_mode object definition to ensure schema-driven tooling and
generated configurations are aligned with the documented runtime expectations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 0b0d9090-5ec7-407d-ace5-a4ae23b1050b
📒 Files selected for processing (14)
framework/configstore/clientconfig.goframework/configstore/migrations.goframework/configstore/rdb.goframework/configstore/store.goframework/configstore/tables/clientconfig.goframework/configstore/tables/oauth2_server.goframework/temptoken/scope.gotransports/bifrost-http/handlers/config.gotransports/bifrost-http/handlers/oauth2_discovery.gotransports/bifrost-http/handlers/oauth2_utils.gotransports/bifrost-http/handlers/plugins.gotransports/bifrost-http/handlers/plugins_test.gotransports/bifrost-http/server/server.gotransports/config.schema.json
226ccda to
bd05880
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@framework/configstore/rdb.go`:
- Around line 6709-6710: In the createOAuth2SigningKey function, before the
final reload attempt (the call to loadOAuth2SigningKey after handling insert
conflicts), add logic to repair any existing empty rows in the database. When an
insert conflict occurs, check if an empty row already exists (where Value is an
empty string or NULL), and if so, update that row with the newly generated key
data before reloading. This repair step should be applied in both locations
mentioned in the issue (around line 6709-6710 in the loadOAuth2SigningKey check
and also around line 6775), preventing the infinite retry loop that occurs when
loadOAuth2SigningKey returns ErrNotFound for empty rows.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 110527b2-c815-4a08-8d76-630f8379c0ad
📒 Files selected for processing (14)
framework/configstore/clientconfig.goframework/configstore/migrations.goframework/configstore/rdb.goframework/configstore/store.goframework/configstore/tables/clientconfig.goframework/configstore/tables/oauth2_server.goframework/temptoken/scope.gotransports/bifrost-http/handlers/config.gotransports/bifrost-http/handlers/middlewares.gotransports/bifrost-http/handlers/oauth2_discovery.gotransports/bifrost-http/handlers/oauth2_utils.gotransports/bifrost-http/lib/config_test.gotransports/bifrost-http/server/server.gotransports/config.schema.json
🚧 Files skipped from review as they are similar to previous changes (7)
- transports/bifrost-http/handlers/oauth2_utils.go
- transports/bifrost-http/server/server.go
- transports/config.schema.json
- framework/configstore/store.go
- framework/temptoken/scope.go
- transports/bifrost-http/handlers/oauth2_discovery.go
- framework/configstore/tables/clientconfig.go
18d1803 to
67e3649
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@transports/bifrost-http/handlers/oauth2_utils.go`:
- Around line 27-29: The oauth2MCPResourceURL function concatenates the issuer
URL with "/mcp" without normalizing trailing slashes, which results in "//mcp"
when the issuer URL is configured with a trailing slash. This causes resource
URL mismatches that break token validation. Fix this by trimming any trailing
slash from the result of oauth2IssuerURL(ctx, store) before appending "/mcp" to
ensure consistent, canonical resource URLs across discovery, authorization, and
verification paths.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: f8dbb913-0854-4528-8915-13626b1d3311
📒 Files selected for processing (14)
framework/configstore/clientconfig.goframework/configstore/migrations.goframework/configstore/rdb.goframework/configstore/store.goframework/configstore/tables/clientconfig.goframework/configstore/tables/oauth2_server.goframework/temptoken/scope.gotransports/bifrost-http/handlers/config.gotransports/bifrost-http/handlers/middlewares.gotransports/bifrost-http/handlers/oauth2_discovery.gotransports/bifrost-http/handlers/oauth2_utils.gotransports/bifrost-http/lib/config_test.gotransports/bifrost-http/server/server.gotransports/config.schema.json
🚧 Files skipped from review as they are similar to previous changes (13)
- framework/temptoken/scope.go
- transports/bifrost-http/handlers/middlewares.go
- framework/configstore/clientconfig.go
- transports/bifrost-http/server/server.go
- framework/configstore/migrations.go
- transports/config.schema.json
- framework/configstore/store.go
- transports/bifrost-http/lib/config_test.go
- transports/bifrost-http/handlers/config.go
- framework/configstore/tables/clientconfig.go
- framework/configstore/tables/oauth2_server.go
- framework/configstore/rdb.go
- transports/bifrost-http/handlers/oauth2_discovery.go
## Summary Fixes a bug where OTEL plugin headers were being overwritten with redacted placeholder values when saving a plugin configuration. After the multi-profile change, header values stored as plain strings inside the `profiles` array were not being restored from the database before saving, causing real credentials to be replaced with masked values like `****`. ## Changes - Extracted `restoreRedactedValue` as a standalone recursive helper, replacing the inline logic in `restoreRedactedFromExisting`. This allows the restoration logic to descend into both nested maps and slices. - Added slice traversal support (index-aligned) so that elements within arrays like the OTEL `profiles` array are individually checked and restored. - Added plain-string redaction detection so that header values stored as raw strings (rather than `EnvVar` objects) are also restored from the existing DB config when they carry a redaction artifact. Empty strings are intentionally left as-is to allow clearing a value. - Added `TestRestoreRedacted_OTELProfilesHeaders` to cover both failure modes: slice traversal and plain-string secret restoration. Also asserts that genuinely new (non-redacted) values pass through unchanged. ## Type of change - [x] Bug fix - [ ] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [ ] UI (React) - [ ] Docs ## How to test ```sh go test ./transports/bifrost-http/handlers/... ``` Verify that saving an OTEL plugin configuration with multiple profiles, after a GET that returns redacted header values, does not overwrite the stored credentials in the database. Confirm that providing a genuinely new header value still persists correctly. ## Screenshots/Recordings N/A ## Breaking changes - [ ] Yes - [x] No ## Related issues N/A ## Security considerations This fix ensures that redacted credential placeholders returned to the client are never written back over real secrets stored in the database. The restoration logic only replaces values that are confirmed redaction artifacts; empty strings and non-redacted values are always passed through as-is, preserving the ability to clear a credential intentionally. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
67e3649 to
46280b1
Compare
The merge-base changed after approval.

Summary
This PR introduces the foundational OAuth 2.1 authorization server infrastructure for Bifrost's
/mcpendpoint. It adds a configurablemcp_server_auth_modethat controls how inbound MCP clients are authenticated, enabling Bifrost to act as a spec-compliant OAuth 2.1 AS with RFC-mandated discovery endpoints, a JWKS endpoint, and a persistent RS256 signing keypair.Changes
MCPServerAuthMode— newvarcharcolumn onTableClientConfigwith three modes:headers(default): existing VK/api-key/session header auth only; discovery endpoints return 404.both: accepts both header credentials and Bifrost-issued JWTs; discovery endpoints are live.oauth: Bifrost JWTs only; header credentials are rejected on/mcp. Breaking for existing VK-based MCP integrations.OAuth2ServerConfig— new JSON blob column onTableClientConfigholding AS-specific settings (IssuerURL,AuthCodeTTL,AccessTokenTTL). Serialized viaBeforeSave/AfterFindhooks. Only meaningful when mode isbothoroauth.OAuth2SigningKey— RS2048 keypair generated on first use and persisted ingovernance_configunderoauth2_signing_key. The private key PEM is encrypted at rest viaframework/encryptwhen encryption is enabled.GetOAuth2SigningKey— newConfigStoreinterface method that lazily generates and persists the signing keypair on first call, always returning a usable key.OAuth2DiscoveryHandler— serves the three well-known discovery endpoints:GET /.well-known/oauth-protected-resource[/{path}](RFC 9728)GET /.well-known/oauth-authorization-server[/{path}](RFC 8414)GET /.well-known/jwks.json(RFC 7517)All three return 404 when
MCPServerAuthModeisheaders. Routes are always registered; the mode flag is the feature toggle.oauth2IssuerURL/oauth2ServerCfg— utility helpers that resolve the effective issuer URL (configuredIssuerURLor request-derived fallback) and AS config defaults.OAuth2ConsentScopeName— new temp-token scope for binding browser sessions to pending authorization requests on the public consent page.add_oauth2_server_tables— addsmcp_server_auth_modeandoauth2_server_config_jsoncolumns toconfig_client.mcp_server_auth_modeandoauth2_server_configadded toconfig.schema.jsonwith full descriptions and validation.IsMCPOAuthDiscoveryEnabled— helper onClientConfigthat returns true when mode isbothoroauth.Type of change
Affected areas
How to test
go test ./framework/configstore/... ./transports/bifrost-http/...mcp_server_auth_modeunset (or"headers"). ConfirmGET /.well-known/oauth-authorization-serverreturns 404.mcp_server_auth_modeto"both"and restart. Confirm:GET /.well-known/oauth-authorization-serverreturns a valid JSON document withissuer,authorization_endpoint,token_endpoint, etc.GET /.well-known/oauth-protected-resourcereturns a document pointing to/mcp.GET /.well-known/jwks.jsonreturns a JWKS with one RS256 key entry.governance_configand survives a restart (samekidreturned).mcp_server_auth_modeto"oauth"and confirm header-credential MCP requests are rejected.New config fields:
mcp_server_auth_mode"headers"|"both"|"oauth""headers"oauth2_server_config.issuer_urloauth2_server_config.auth_code_ttl600oauth2_server_config.access_token_ttl600Breaking changes
Setting
mcp_server_auth_modeto"oauth"disables VK/api-key/session header authentication on/mcp. Existing virtual-key MCP integrations will stop working. Use"both"for a non-breaking migration path that accepts both credential types simultaneously.Security considerations
framework/encryptwhen encryption is enabled. The plaintext key is only held in memory during the signing operation.OAuth2ConsentScopeNametemp token is the sole credential binding a browser session to a pending authorization request on the public (unauthenticated) consent page — it must be treated as a short-lived secret.issuer_url; omitting it causes token verification failures when theHostheader differs across nodes.Checklist
docs/contributing/README.mdand followed the guidelines