[PR #8632] [MERGED] fix(oauth-provider): scope loss on PAR, loopback redirect matching, DCR skip_consent #25005

Closed
opened 2026-04-15 22:41:15 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/8632
Author: @gustavovalverde
Created: 3/16/2026
Status: Merged
Merged: 4/6/2026
Merged by: @gustavovalverde

Base: mainHead: fix/oauth-provider-par-loopback-dcr


📝 Commits (3)

  • 79e0be8 fix(oauth-provider): PAR scope loss, loopback redirect matching, DCR skip_consent
  • 3d9f205 fix(oauth-provider): carry request_uri params through redirects
  • f286484 fix(oauth-provider): harden PAR param isolation, loopback query matching

📊 Changes

9 files changed (+555 additions, -13 deletions)

View changed files

.changeset/fix-oauth-provider-par-loopback-dcr.md (+10 -0)
📝 packages/oauth-provider/src/authorize.test.ts (+116 -0)
📝 packages/oauth-provider/src/authorize.ts (+66 -8)
📝 packages/oauth-provider/src/oauth.ts (+12 -2)
📝 packages/oauth-provider/src/register.test.ts (+40 -0)
📝 packages/oauth-provider/src/register.ts (+8 -0)
📝 packages/oauth-provider/src/token.test.ts (+283 -0)
📝 packages/oauth-provider/src/token.ts (+1 -1)
📝 packages/oauth-provider/src/types/index.ts (+19 -2)

📄 Description

Problem

Three independent issues in oauth-provider's authorization and registration flows share a root cause: the authorize and DCR endpoints don't fully account for how PAR, native apps, and programmatic registration interact with the existing validation logic.

PAR scope loss

redirectWithAuthorizationCode stores ctx.query (the raw URL params) instead of verificationValue.query (the PAR-resolved params). When PAR is used, the authorize endpoint resolves request_uri into stored parameters, but this resolution is lost when the authorization code is created. The code exchange then sees the original (sparse) query instead of the full PAR payload, losing scopes, nonce, and other PAR-carried parameters.

Loopback port matching

RFC 8252 §7.3 requires loopback redirect URIs (127.0.0.1, [::1]) to match by scheme, host, and path, ignoring port. Native apps bind to ephemeral ports, so the port at registration time differs from the port at runtime. The current exact-match comparison rejects valid native app redirects.

The admin register endpoint accepts skip_consent in its Zod schema, but the DCR register endpoint does not. Programmatic client registration cannot set this field.

Changes

  • authorize.ts: store verificationValue.query instead of ctx.query when creating the authorization code
  • authorize.ts: port-agnostic redirect URI matching for loopback IPs per RFC 8252 §7.3
  • oauth.ts: add skip_consent: z.boolean().optional() to DCR register body schema

References


Summary by cubic

Fixes PAR parameter loss by resolving request_uri and isolating its params, adds RFC 8252-compliant loopback redirect matching (includes query), and blocks skip_consent during DCR in @better-auth/oauth-provider. This preserves requested scopes and supports native app redirects.

  • Bug Fixes
    • PAR: resolve via requestUriResolver, replace URL params with stored PAR request (carry only client_id), discard front‑channel extras, strip request_uri from redirects, persist resolved params into the auth code, and return consistent redirect errors on invalid request_uri; fix query serialization to keep non-strings (e.g., max_age); OpenAPI: request_uri added and response_type optional.
    • Redirect validation: loopback (127.0.0.1, [::1]) must match scheme/host/path/query; port ignored. Reject non-loopback mismatches. Clearer token error on redirect_uri mismatch.
    • DCR: schema accepts skip_consent, but dynamic registration rejects it to prevent escalation; admin-created clients remain allowed.

Written for commit f286484bae. Summary will update on new commits.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/8632 **Author:** [@gustavovalverde](https://github.com/gustavovalverde) **Created:** 3/16/2026 **Status:** ✅ Merged **Merged:** 4/6/2026 **Merged by:** [@gustavovalverde](https://github.com/gustavovalverde) **Base:** `main` ← **Head:** `fix/oauth-provider-par-loopback-dcr` --- ### 📝 Commits (3) - [`79e0be8`](https://github.com/better-auth/better-auth/commit/79e0be8564fe2dffaabc1da970e1fe670882d276) fix(oauth-provider): PAR scope loss, loopback redirect matching, DCR skip_consent - [`3d9f205`](https://github.com/better-auth/better-auth/commit/3d9f2058a46cfcf6a9dff7ed44d3807ca011f6f3) fix(oauth-provider): carry request_uri params through redirects - [`f286484`](https://github.com/better-auth/better-auth/commit/f286484bae03fd3b7f1f9b4bd42c3b58841350ad) fix(oauth-provider): harden PAR param isolation, loopback query matching ### 📊 Changes **9 files changed** (+555 additions, -13 deletions) <details> <summary>View changed files</summary> ➕ `.changeset/fix-oauth-provider-par-loopback-dcr.md` (+10 -0) 📝 `packages/oauth-provider/src/authorize.test.ts` (+116 -0) 📝 `packages/oauth-provider/src/authorize.ts` (+66 -8) 📝 `packages/oauth-provider/src/oauth.ts` (+12 -2) 📝 `packages/oauth-provider/src/register.test.ts` (+40 -0) 📝 `packages/oauth-provider/src/register.ts` (+8 -0) 📝 `packages/oauth-provider/src/token.test.ts` (+283 -0) 📝 `packages/oauth-provider/src/token.ts` (+1 -1) 📝 `packages/oauth-provider/src/types/index.ts` (+19 -2) </details> ### 📄 Description ## Problem Three independent issues in oauth-provider's authorization and registration flows share a root cause: the authorize and DCR endpoints don't fully account for how PAR, native apps, and programmatic registration interact with the existing validation logic. ### PAR scope loss `redirectWithAuthorizationCode` stores `ctx.query` (the raw URL params) instead of `verificationValue.query` (the PAR-resolved params). When PAR is used, the authorize endpoint resolves `request_uri` into stored parameters, but this resolution is lost when the authorization code is created. The code exchange then sees the original (sparse) query instead of the full PAR payload, losing scopes, nonce, and other PAR-carried parameters. ### Loopback port matching RFC 8252 §7.3 requires loopback redirect URIs (`127.0.0.1`, `[::1]`) to match by scheme, host, and path, ignoring port. Native apps bind to ephemeral ports, so the port at registration time differs from the port at runtime. The current exact-match comparison rejects valid native app redirects. ### DCR skip_consent The admin register endpoint accepts `skip_consent` in its Zod schema, but the DCR register endpoint does not. Programmatic client registration cannot set this field. ## Changes - `authorize.ts`: store `verificationValue.query` instead of `ctx.query` when creating the authorization code - `authorize.ts`: port-agnostic redirect URI matching for loopback IPs per RFC 8252 §7.3 - `oauth.ts`: add `skip_consent: z.boolean().optional()` to DCR register body schema ## References - [RFC 8252 §7.3 — Loopback Interface Redirection](https://datatracker.ietf.org/doc/html/rfc8252#section-7.3) - [RFC 9126 — Pushed Authorization Requests](https://datatracker.ietf.org/doc/html/rfc9126) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Fixes PAR parameter loss by resolving `request_uri` and isolating its params, adds RFC 8252-compliant loopback redirect matching (includes query), and blocks `skip_consent` during DCR in `@better-auth/oauth-provider`. This preserves requested scopes and supports native app redirects. - **Bug Fixes** - PAR: resolve via `requestUriResolver`, replace URL params with stored PAR request (carry only `client_id`), discard front‑channel extras, strip `request_uri` from redirects, persist resolved params into the auth code, and return consistent redirect errors on invalid `request_uri`; fix query serialization to keep non-strings (e.g., `max_age`); OpenAPI: `request_uri` added and `response_type` optional. - Redirect validation: loopback (`127.0.0.1`, `[::1]`) must match scheme/host/path/query; port ignored. Reject non-loopback mismatches. Clearer token error on `redirect_uri` mismatch. - DCR: schema accepts `skip_consent`, but dynamic registration rejects it to prevent escalation; admin-created clients remain allowed. <sup>Written for commit f286484bae03fd3b7f1f9b4bd42c3b58841350ad. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-15 22:41:15 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#25005