Skip to content

feat: add OAuth2 issuance endpoints (DCR, authorize, token) with PKCE and refresh token rotation#4506

Open
Pratham-Mishra04 wants to merge 1 commit into
06-16-feat_adds_mcp_server_oauth_tablesfrom
06-17-feat_adds_mcp_oauth2_authorization_server
Open

feat: add OAuth2 issuance endpoints (DCR, authorize, token) with PKCE and refresh token rotation#4506
Pratham-Mishra04 wants to merge 1 commit into
06-16-feat_adds_mcp_server_oauth_tablesfrom
06-17-feat_adds_mcp_oauth2_authorization_server

Conversation

@Pratham-Mishra04

Copy link
Copy Markdown
Collaborator

Summary

This PR implements the downstream OAuth2 token issuance flow, enabling Bifrost to act as a full OAuth2 authorization server. It adds the three core RFC-compliant endpoints (Dynamic Client Registration, Authorization, and Token), the backing database tables and store methods, and the consent temp-token scope needed to bind the consent UI to a specific authorization request.

Changes

  • New DB tables (oauth2_clients, oauth2_authorize_requests, oauth2_refresh_tokens) added via a new add_oauth2_issuance_tables migration step, with GORM model definitions in tables/oauth2_issuance.go. TableOAuth2Client serializes redirect_uris and grant_types as JSON columns with BeforeSave/AfterFind hooks.
  • ConfigStore interface extended with methods for creating/fetching OAuth2 clients, managing authorize requests (including code-hash lookup and expiry sweeping), and refresh token operations (GetOAuth2RefreshTokenByHash, ConsumeOAuth2AuthorizeRequest, RotateOAuth2RefreshToken).
  • RDBConfigStore implements all new interface methods. ConsumeOAuth2AuthorizeRequest and RotateOAuth2RefreshToken are wrapped in transactions so failures leave the grant in a retryable state.
  • OAuth2IssuanceHandler (handlers/oauth2_issuance.go) wires three public routes:
    • POST /oauth2/register — RFC 7591 DCR; only public clients (token_endpoint_auth_method=none) are accepted.
    • GET /oauth2/authorize — PKCE-S256 (RFC 7636) + resource indicator (RFC 8707); creates a pending authorize request and redirects to the consent UI with an optional scoped temp token.
    • POST /oauth2/token — handles authorization_code and refresh_token grants; issues RS256 JWTs signed with the persisted signing key and rotates refresh tokens on every use.
  • Stolen-token detection via FamilyID on refresh tokens: all tokens descended from the same authorization grant share a family ID, enabling full family revocation when a revoked token is re-presented (RFC 9700 §2.2.2).
  • Loopback redirect URI matching follows RFC 8252 §7.3 (port-agnostic for localhost/127.0.0.1).
  • oauth2ConsentScope temp-token scope registered at startup, binding consent-page API calls to a single authorize request ID via path substitution.
  • Server bootstrap pre-warms the OAuth2 signing key when MCP OAuth discovery is enabled, so JWKS and JWT signing are ready before the first request.
  • github.com/golang-jwt/jwt/v5 promoted from indirect to a direct dependency.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./framework/configstore/... ./transports/bifrost-http/...
  1. Start Bifrost with MCP OAuth discovery enabled and a configured config store.
  2. Register a client:
    curl -X POST http://localhost:PORT/oauth2/register \
      -H "Content-Type: application/json" \
      -d '{"client_name":"test","redirect_uris":["http://localhost:8080/callback"]}'
  3. Initiate an authorization request via GET /oauth2/authorize with response_type=code, code_challenge (S256), resource, and the returned client_id.
  4. Complete the consent flow and exchange the auth code at POST /oauth2/token with grant_type=authorization_code and the PKCE verifier.
  5. Refresh the access token using grant_type=refresh_token and verify the old refresh token is revoked and a new one is issued.

Breaking changes

  • No

Security considerations

  • Refresh tokens are stored as SHA-256 hashes only; plaintext is returned to the client once and never persisted.
  • Auth codes are single-use: ConsumeOAuth2AuthorizeRequest atomically transitions the request to code_issued and creates the refresh token in one transaction.
  • Refresh token rotation is atomic; if rotation fails the old token remains valid and the client can retry safely.
  • Stolen-token detection revokes the entire token family when a previously-rotated (revoked) token is presented, per RFC 9700 §2.2.2.
  • Only public clients are supported; no client secrets are accepted or stored.
  • PKCE S256 is mandatory; plain challenge method is rejected.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Pratham-Mishra04 commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 39af3608-4b75-4d8a-b5d2-3fa0fbfd7b7e

📥 Commits

Reviewing files that changed from the base of the PR and between 4eb43f0 and d37e5e4.

📒 Files selected for processing (9)
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/oauth2_issuance.go
  • transports/bifrost-http/handlers/oauth2_issuance.go
  • transports/bifrost-http/handlers/temp_token_scopes.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
🚧 Files skipped from review as they are similar to previous changes (9)
  • transports/bifrost-http/handlers/temp_token_scopes.go
  • transports/go.mod
  • transports/bifrost-http/lib/config_test.go
  • framework/configstore/tables/oauth2_issuance.go
  • transports/bifrost-http/server/server.go
  • framework/configstore/migrations.go
  • framework/configstore/store.go
  • framework/configstore/rdb.go
  • transports/bifrost-http/handlers/oauth2_issuance.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added OAuth2 dynamic client registration endpoint enabling clients to register programmatically.
    • Added OAuth2 authorization endpoint supporting PKCE (RFC 7591), resource parameters, and redirect URI validation.
    • Added OAuth2 token endpoint with authorization code and refresh token grant flows.
    • Added consent flow with temporary scoped tokens for authorization request approval.
    • Added atomic refresh token rotation with expiration sweeping.

Walkthrough

Adds complete OAuth2 issuance support to Bifrost: three new GORM-backed database tables (oauth2_clients, oauth2_authorize_requests, oauth2_refresh_tokens) with database migration, ten new ConfigStore interface methods with RDB implementations including atomic transactional state transitions, a full HTTP handler covering RFC 7591 DCR and RFC 6749 authorization flows with PKCE-S256 verification and RS256 JWT token issuance, plus consent temporary-token scope and server signing-key bootstrap wiring.

Changes

OAuth2 Issuance Feature

Layer / File(s) Summary
GORM models and DB migration
framework/configstore/tables/oauth2_issuance.go, framework/configstore/migrations.go
Defines TableOAuth2Client with JSON-backed redirect URIs and grant types (via BeforeSave/AfterFind GORM hooks), TableOAuth2AuthorizeRequest with status enum (pending/consented/code_issued), optional code hash, and TTL expiry, and TableOAuth2RefreshToken with family ID and revocation tracking. Adds migration step add_oauth2_issuance_tables that conditionally creates all three tables with explicit rollback.
ConfigStore interface and RDB persistence
framework/configstore/store.go, framework/configstore/rdb.go, transports/bifrost-http/lib/config_test.go
Extends ConfigStore with ten OAuth2 methods (client CRUD, authorize-request CRUD with code-hash lookup and TTL sweep, refresh-token fetch, atomic consume/rotate); implements on RDBConfigStore with error handling and transactional guarantees; adds test mocks implementing the interface.
OAuth2 HTTP handler structure and endpoints
transports/bifrost-http/handlers/oauth2_issuance.go
Implements OAuth2IssuanceHandler with POST /oauth2/register (RFC 7591 DCR), GET /oauth2/authorize (PKCE-S256, RFC 8707 resource, loopback port-agnostic URI matching, optional consent temp-token), POST /oauth2/token dispatch entry point.
OAuth2 grant flow implementations
transports/bifrost-http/handlers/oauth2_issuance.go
Implements authorization_code exchange (code hash verification, PKCE-S256 validation, expiry check, atomic token consume) and refresh_token exchange (refresh-token hash lookup, client/resource validation, atomic token rotation).
Token issuance core and cryptographic helpers
transports/bifrost-http/handlers/oauth2_issuance.go
Implements issueTokenPair for RS256 JWT signing with PKCS#8 RSA key parsing and claims construction; provides helpers: matchRedirectURI (loopback port-agnostic), verifyPKCES256 (constant-time PKCE validation), generateSecureToken (secure token generation), parseRSAPrivateKeyPEM (PKCS#8 key parsing), sendTokenResponse (RFC 6749 serialization), and sendOAuthError (OAuth JSON errors).
Consent scope, route registration, and bootstrap
transports/bifrost-http/handlers/temp_token_scopes.go, transports/bifrost-http/server/server.go, transports/go.mod
Adds oauth2ConsentScope (15-min TTL, GET/PUT on /api/oauth2/consent/flows/{id}), registers it in RegisterTempTokenScopes; wires OAuth2IssuanceHandler routes in RegisterAPIRoutes after OAuth2 discovery routes; bootstraps OAuth2 signing key in Bootstrap when MCP discovery is enabled; promotes golang-jwt/jwt/v5 to a direct dependency.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Handler as OAuth2IssuanceHandler
  participant ConfigStore
  participant TempToken as TempTokenService

  rect rgba(173, 216, 230, 0.5)
    note over Client,TempToken: Authorization Request
    Client->>Handler: GET /oauth2/authorize?client_id&code_challenge&resource
    Handler->>ConfigStore: GetOAuth2ClientByClientID
    Handler->>Handler: matchRedirectURI (loopback port-agnostic)
    Handler->>ConfigStore: CreateOAuth2AuthorizeRequest
    Handler->>TempToken: MintTempToken (oauth2_consent, 15-min TTL)
    Handler-->>Client: 302 Found → consent page
  end

  rect rgba(144, 238, 144, 0.5)
    note over Client,ConfigStore: Token Exchange (authorization_code)
    Client->>Handler: POST /oauth2/token grant_type=authorization_code&code&code_verifier&client_id
    Handler->>Handler: hashSHA256Hex(code)
    Handler->>ConfigStore: GetOAuth2AuthorizeRequestByCodeHash
    Handler->>Handler: verifyPKCES256(code_verifier vs. stored challenge)
    Handler->>ConfigStore: GetOAuth2SigningKey → RS256 JWT
    Handler->>ConfigStore: ConsumeOAuth2AuthorizeRequest (atomic: status→code_issued + insert refresh token)
    Handler-->>Client: access_token, refresh_token, scope, expires_in
  end

  rect rgba(255, 218, 185, 0.5)
    note over Client,ConfigStore: Token Rotation (refresh_token)
    Client->>Handler: POST /oauth2/token grant_type=refresh_token&refresh_token&client_id
    Handler->>Handler: hashSHA256Hex(refresh_token)
    Handler->>ConfigStore: GetOAuth2RefreshTokenByHash
    Handler->>ConfigStore: GetOAuth2SigningKey → RS256 JWT
    Handler->>ConfigStore: RotateOAuth2RefreshToken (atomic: revoke old + insert new)
    Handler-->>Client: new access_token, rotated refresh_token, expires_in
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

  • akshaydeo
  • danpiths

Poem

🐰 A rabbit hops through RFC land,
With PKCE codes close at hand,
JWT tokens signed in RS256 style,
Refresh tokens rotate—secure all the while,
OAuth2 consent, a 15-minute key,
Three new tables bloom for all to see! 🗝️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main feature: OAuth2 issuance endpoints with DCR, authorization, PKCE, and refresh token rotation.
Description check ✅ Passed The PR description is comprehensive, covering all major template sections including summary, detailed changes, type of change, affected areas, testing instructions, security considerations, and most checklist items.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 06-17-feat_adds_mcp_oauth2_authorization_server

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 @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot requested a review from roroghost17 June 17, 2026 21:12
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 3/5

The DB layer and atomic token operations are well-constructed, but two defects in the HTTP handler layer should be fixed before this ships: the token endpoint responses lack the RFC-mandated cache suppression headers, and the DCR endpoint stores redirect URIs without validating their scheme, allowing javascript: URIs to be registered and later echoed into HTTP Location redirects.

Two distinct security gaps exist in the handler: the token response cache-control omission and the unrestricted redirect URI scheme at DCR registration. Both affect the same new handler file and are straightforward to fix, but they represent real security defects in an OAuth2 AS — not theoretical risks — so they warrant attention before merging.

transports/bifrost-http/handlers/oauth2_issuance.go — both the sendTokenResponse helper and the handleRegister function need fixes before this ships.

Important Files Changed

Filename Overview
transports/bifrost-http/handlers/oauth2_issuance.go New 581-line handler implementing DCR, authorize, and token endpoints; missing Cache-Control: no-store on token responses (RFC 6749 §5.1) and missing redirect URI scheme validation in DCR that allows javascript: URIs to be registered.
framework/configstore/tables/oauth2_issuance.go Adds TableOAuth2Client, TableOAuth2AuthorizeRequest, and TableOAuth2RefreshToken GORM models; CodeHash is correctly a *string pointer to allow NULL in the unique index, BeforeSave/AfterFind hooks correctly serialize JSON arrays.
framework/configstore/rdb.go Adds RDB implementations for all new store interface methods; ConsumeOAuth2AuthorizeRequest and RotateOAuth2RefreshToken correctly use transactions with RowsAffected checks to prevent double-spend and concurrent rotation races.
framework/configstore/migrations.go Adds migrationAddOAuth2IssuanceTables step creating three new tables with rollback; uses HasTable guards to avoid re-running on existing schemas.
transports/bifrost-http/server/server.go Registers OAuth2IssuanceHandler routes and pre-warms the signing key at bootstrap when MCP OAuth discovery is enabled; signing key failure is non-fatal (logs warning) which is acceptable since GetOAuth2SigningKey will retry on first request.
framework/configstore/store.go ConfigStore interface extended with 8 new OAuth2 methods; interface doc comments are accurate and match RDB implementations.
transports/bifrost-http/handlers/temp_token_scopes.go Adds oauth2ConsentScope binding GET/PUT consent flow routes to a single authorize request ID via {id} path substitution; max TTL correctly matches auth code TTL.
transports/bifrost-http/lib/config_test.go MockConfigStore stub implementations added for all new interface methods; stubs return ErrNotFound or nil as appropriate for compilation.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant C as OAuth2 Client
    participant B as Bifrost (/oauth2/*)
    participant DB as ConfigStore (DB)
    participant UI as Consent UI

    C->>B: "POST /oauth2/register {redirect_uris, ...}"
    B->>DB: CreateOAuth2Client
    B-->>C: "201 {client_id, ...}"

    C->>B: "GET /oauth2/authorize?client_id&code_challenge&resource"
    B->>DB: GetOAuth2ClientByClientID
    B->>DB: "CreateOAuth2AuthorizeRequest (status=pending)"
    B->>B: Mint temp token (scoped to request ID)
    B-->>C: "302 -> /oauth/consent?flow={id}#t={tempToken}"

    C->>UI: Load consent page
    UI->>B: "PUT /api/oauth2/consent/flows/{id} (auth: temp token)"
    B->>DB: "ConsentOAuth2AuthorizeRequest (status->consented, code_hash set)"
    UI-->>C: "302 -> redirect_uri?code={authCode}&state"

    C->>B: "POST /oauth2/token {code, code_verifier, client_id}"
    B->>DB: "GetOAuth2AuthorizeRequestByCodeHash (status=consented)"
    B->>B: verifyPKCES256(code_verifier, stored_challenge)
    B->>B: "issueTokenPair -> sign JWT + generate refresh token"
    B->>DB: "ConsumeOAuth2AuthorizeRequest (status->code_issued, create refresh token)"
    B-->>C: "{access_token, refresh_token, expires_in}"

    C->>B: "POST /oauth2/token {refresh_token, client_id}"
    B->>DB: GetOAuth2RefreshTokenByHash (revoked_at IS NULL)
    B->>B: "issueTokenPair -> new JWT + new refresh token"
    B->>DB: RotateOAuth2RefreshToken (revoke old, create new)
    B-->>C: "{access_token, new_refresh_token, expires_in}"
Loading
%%{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 C as OAuth2 Client
    participant B as Bifrost (/oauth2/*)
    participant DB as ConfigStore (DB)
    participant UI as Consent UI

    C->>B: "POST /oauth2/register {redirect_uris, ...}"
    B->>DB: CreateOAuth2Client
    B-->>C: "201 {client_id, ...}"

    C->>B: "GET /oauth2/authorize?client_id&code_challenge&resource"
    B->>DB: GetOAuth2ClientByClientID
    B->>DB: "CreateOAuth2AuthorizeRequest (status=pending)"
    B->>B: Mint temp token (scoped to request ID)
    B-->>C: "302 -> /oauth/consent?flow={id}#t={tempToken}"

    C->>UI: Load consent page
    UI->>B: "PUT /api/oauth2/consent/flows/{id} (auth: temp token)"
    B->>DB: "ConsentOAuth2AuthorizeRequest (status->consented, code_hash set)"
    UI-->>C: "302 -> redirect_uri?code={authCode}&state"

    C->>B: "POST /oauth2/token {code, code_verifier, client_id}"
    B->>DB: "GetOAuth2AuthorizeRequestByCodeHash (status=consented)"
    B->>B: verifyPKCES256(code_verifier, stored_challenge)
    B->>B: "issueTokenPair -> sign JWT + generate refresh token"
    B->>DB: "ConsumeOAuth2AuthorizeRequest (status->code_issued, create refresh token)"
    B-->>C: "{access_token, refresh_token, expires_in}"

    C->>B: "POST /oauth2/token {refresh_token, client_id}"
    B->>DB: GetOAuth2RefreshTokenByHash (revoked_at IS NULL)
    B->>B: "issueTokenPair -> new JWT + new refresh token"
    B->>DB: RotateOAuth2RefreshToken (revoke old, create new)
    B-->>C: "{access_token, new_refresh_token, expires_in}"
Loading

Reviews (6): Last reviewed commit: "feat: adds mcp oauth2 authorization serv..." | Re-trigger Greptile

Comment thread framework/configstore/tables/oauth2_issuance.go Outdated
Comment thread framework/configstore/rdb.go Outdated
Comment thread transports/bifrost-http/handlers/oauth2_issuance.go
Comment thread transports/bifrost-http/handlers/oauth2_issuance.go
Comment thread transports/bifrost-http/handlers/oauth2_issuance.go Outdated
Comment thread transports/bifrost-http/handlers/oauth2_issuance.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (2)
framework/configstore/tables/oauth2_issuance.go (1)

13-25: ⚡ Quick win

Consider adding UpdatedAt for audit trail.

While OAuth2 clients are relatively static after registration, tracking the last update time can be useful for audit, troubleshooting, and compliance purposes (e.g., when client_name or redirect_uris are modified).

📝 Suggested addition
 	Scope            string    `gorm:"type:varchar(255)" json:"scope"`
 	CreatedAt        time.Time `gorm:"index;not null" json:"created_at"`
+	UpdatedAt        time.Time `gorm:"not null" json:"updated_at"`
 
 	// Virtual fields

GORM will automatically populate UpdatedAt on every save.

🤖 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 `@framework/configstore/tables/oauth2_issuance.go` around lines 13 - 25, The
TableOAuth2Client struct is missing an UpdatedAt field for audit trail tracking.
Add an UpdatedAt field of type time.Time to the struct with GORM tags that
include indexing (similar to the existing CreatedAt field). GORM will
automatically populate this field on every save operation, providing audit
history for when OAuth2 client records are modified.
transports/bifrost-http/handlers/oauth2_issuance.go (1)

427-476: 💤 Low value

Consider supporting IPv6 loopback in redirect URI matching.

RFC 8252 §7.3 notes that "localhost" should resolve to loopback addresses for both IPv4 and IPv6. The current implementation handles localhost and 127.0.0.1 but not [::1] (IPv6 loopback). While most OAuth clients use IPv4, some environments may prefer IPv6.

🤖 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/bifrost-http/handlers/oauth2_issuance.go` around lines 427 - 476,
The matchRedirectURI function currently only recognizes localhost and 127.0.0.1
as loopback addresses but does not handle IPv6 loopback ([::1]). Update the
loopback detection logic to also check if the parsed hostname equals [::1] in
addition to the existing localhost and 127.0.0.1 checks, ensuring that IPv6
loopback addresses are properly handled alongside IPv4 loopback addresses when
matching redirect URIs.
🤖 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/migrations.go`:
- Around line 10932-10963: The migrationAddOAuth2IssuanceTables function creates
three OAuth2 tables (TableOAuth2Client, TableOAuth2AuthorizeRequest, and
TableOAuth2RefreshToken) but provides no rollback mechanism in the
migrator.Migration struct. Add a Rollback function to the migration that drops
these three tables in reverse order (TableOAuth2RefreshToken first, then
TableOAuth2AuthorizeRequest, then TableOAuth2Client) to properly handle schema
rollbacks, or if rollback is not supported, explicitly mark the migration as
non-rollbackable using the appropriate migrator option.

In `@framework/configstore/rdb.go`:
- Around line 6843-6852: The GetOAuth2RefreshTokenByHash method in
RDBConfigStore filters out revoked tokens with the WHERE clause condition
"revoked_at IS NULL", which prevents the token endpoint from detecting token
reuse and loading the FamilyID for family-wide revocation. Remove the "AND
revoked_at IS NULL" condition from the database query so that the method returns
the token row regardless of revocation status, and allow the caller to check and
handle the revoked status separately.
- Around line 6860-6869: The code updates the OAuth2AuthorizeRequest table
status but does not verify that the update actually modified any rows. If the
request is already consumed, expired, or missing, the update will affect zero
rows yet the code continues to create the refresh token, violating single-use
authorization code semantics. After the Updates call on the
OAuth2AuthorizeRequest table, check the RowsAffected value from the result. If
RowsAffected is zero, return an error before proceeding to the tx.Create(rt)
call that mints the refresh token. This ensures that the refresh token is only
created when the authorization request is successfully transitioned to the
CodeIssued status.
- Around line 6881-6888: The refresh token rotation in the transaction block is
missing a check to ensure only active tokens are revoked. Modify the WHERE
clause in the Update call that sets revoked_at to also include the condition
revoked_at IS NULL to prevent rotating already-revoked tokens. Additionally,
after the Update operation completes, check the RowsAffected property of the
result to verify that exactly one active token was revoked before proceeding to
create the newRT. If RowsAffected is zero, return an appropriate error to
prevent creating a new token when no old token was actually revoked.

In `@framework/configstore/tables/oauth2_issuance.go`:
- Around line 29-45: The BeforeSave method in TableOAuth2Client does not
validate that the required OAuth2 fields are present before marshaling them. Add
validation logic at the beginning of the BeforeSave method to check if either
RedirectURIs or GrantTypes is nil, and return a formatted error message if
either field is missing (these are required per RFC 7591). Import the "fmt"
package at the top of the file to support error message formatting.

In `@transports/bifrost-http/handlers/oauth2_issuance.go`:
- Around line 378-387: The code calls GetOAuth2SigningKey and checks for errors,
but does not verify that the returned signingKey is not nil before accessing its
PrivateKeyPEM field on line 383. Add a nil check immediately after the error
check for GetOAuth2SigningKey to ensure signingKey is not nil before calling
parseRSAPrivateKeyPEM with signingKey.PrivateKeyPEM. If signingKey is nil, send
an appropriate OAuth error response using sendOAuthError with a suitable error
code and description.
- Around line 335-337: The current code incorrectly falls back to using rt.Scope
when the resource parameter is empty, violating RFC 8707 where resource and
scope are orthogonal concepts. Add a Resource field to the
TableOAuth2RefreshToken struct (similar to how Scope is already stored) to
preserve the original resource indicator across token rotations. Then update the
refresh token handling logic to populate and retrieve the Resource field from
the stored token instead of falling back to Scope when the resource parameter is
empty. This ensures the resource/audience binding remains intact across token
rotations.

---

Nitpick comments:
In `@framework/configstore/tables/oauth2_issuance.go`:
- Around line 13-25: The TableOAuth2Client struct is missing an UpdatedAt field
for audit trail tracking. Add an UpdatedAt field of type time.Time to the struct
with GORM tags that include indexing (similar to the existing CreatedAt field).
GORM will automatically populate this field on every save operation, providing
audit history for when OAuth2 client records are modified.

In `@transports/bifrost-http/handlers/oauth2_issuance.go`:
- Around line 427-476: The matchRedirectURI function currently only recognizes
localhost and 127.0.0.1 as loopback addresses but does not handle IPv6 loopback
([::1]). Update the loopback detection logic to also check if the parsed
hostname equals [::1] in addition to the existing localhost and 127.0.0.1
checks, ensuring that IPv6 loopback addresses are properly handled alongside
IPv4 loopback addresses when matching redirect URIs.
🪄 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: 67cf22e7-348f-45b9-82b1-4f1ac1a97df4

📥 Commits

Reviewing files that changed from the base of the PR and between 53a04f6 and a3e6af6.

📒 Files selected for processing (8)
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/oauth2_issuance.go
  • transports/bifrost-http/handlers/oauth2_issuance.go
  • transports/bifrost-http/handlers/temp_token_scopes.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod

Comment thread framework/configstore/migrations.go
Comment thread framework/configstore/rdb.go
Comment thread framework/configstore/rdb.go
Comment thread framework/configstore/rdb.go
Comment thread framework/configstore/tables/oauth2_issuance.go
Comment thread transports/bifrost-http/handlers/oauth2_issuance.go
Comment thread transports/bifrost-http/handlers/oauth2_issuance.go
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 06-17-feat_adds_mcp_oauth2_authorization_server branch from a3e6af6 to 8bebc42 Compare June 18, 2026 07:34
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 06-16-feat_adds_mcp_server_oauth_tables branch 2 times, most recently from 226ccda to bd05880 Compare June 18, 2026 07:37
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 06-17-feat_adds_mcp_oauth2_authorization_server branch from 8bebc42 to 0c48164 Compare June 18, 2026 07:37
Comment thread transports/bifrost-http/handlers/oauth2_issuance.go
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 06-17-feat_adds_mcp_oauth2_authorization_server branch from 0c48164 to 4eb43f0 Compare June 18, 2026 08:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_issuance.go`:
- Around line 488-492: The loopback redirect URI validation is missing a
hostname comparison check despite the comment indicating it should match scheme
plus host plus path. In the conditional statement within the loopback validation
block (where scheme and path are already compared), add a hostname comparison
between parsed.Hostname() and rParsed.Hostname() to ensure that localhost and
127.0.0.1 are treated as distinct hosts, complying with RFC 8252 requirements.
🪄 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: 85ec5950-56c1-48bc-b72f-4ebf0cd2ab81

📥 Commits

Reviewing files that changed from the base of the PR and between 0c48164 and 4eb43f0.

📒 Files selected for processing (9)
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/oauth2_issuance.go
  • transports/bifrost-http/handlers/oauth2_issuance.go
  • transports/bifrost-http/handlers/temp_token_scopes.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
🚧 Files skipped from review as they are similar to previous changes (8)
  • transports/bifrost-http/handlers/temp_token_scopes.go
  • transports/go.mod
  • framework/configstore/tables/oauth2_issuance.go
  • framework/configstore/store.go
  • framework/configstore/migrations.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/server/server.go
  • framework/configstore/rdb.go

Comment thread transports/bifrost-http/handlers/oauth2_issuance.go
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 06-16-feat_adds_mcp_server_oauth_tables branch from bd05880 to 18d1803 Compare June 18, 2026 08:38
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 06-17-feat_adds_mcp_oauth2_authorization_server branch from 4eb43f0 to 02df1ae Compare June 18, 2026 08:38
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 06-16-feat_adds_mcp_server_oauth_tables branch from 67e3649 to 46280b1 Compare June 18, 2026 12:22
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 06-17-feat_adds_mcp_oauth2_authorization_server branch from d37e5e4 to 5117e2b Compare June 18, 2026 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants