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

Closed
opened 2026-04-13 10:30:02 -05:00 by GiteaMirror · 0 comments
Owner

Original Pull Request: https://github.com/better-auth/better-auth/pull/8632

State: closed
Merged: Yes


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.

**Original Pull Request:** https://github.com/better-auth/better-auth/pull/8632 **State:** closed **Merged:** Yes --- ## 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. -->
GiteaMirror added the pull-request label 2026-04-13 10:30:02 -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#16351