Skip to content

[6.x] OAuth Improvements#14899

Open
jasonvarga wants to merge 24 commits into
6.xfrom
improve-oauth
Open

[6.x] OAuth Improvements#14899
jasonvarga wants to merge 24 commits into
6.xfrom
improve-oauth

Conversation

@jasonvarga

Copy link
Copy Markdown
Member

This reworks how OAuth sign-ins resolve to user accounts and adds the ability to connect/disconnect providers to an existing account.

Account matching

Matching an OAuth sign-in to an existing user by email address has been removed. Email-based matching is fragile — it assumes the email a provider reports is canonical and verified, which isn't always true, and providers can change or reuse them. Relying on it to identify accounts can be insecure.

Sign-ins now resolve a user by their stored provider ID only. If someone signs in with a provider whose email matches an existing account they haven't linked, they'll be asked to sign in to that account and connect the provider from their account settings instead.

Connecting providers

There is now a page in the control panel for you to manage connections to OAuth accounts (linked from the user edit dropdown, alongside Passkeys).

CleanShot 2026-06-29 at 17 55 12 CleanShot 2026-06-29 at 17 53 37

Front-end tags

For themes that want to build their own connected-accounts UI:

  • {{ oauth }} ... {{ /oauth }} — loops the available providers. Each item gets name, label, connected, and url.
  • {{ oauth:disconnect_form provider="..." }} — outputs a form to disconnect a provider
  • The existing {{ oauth:provider_handle }} functions as a "connect" url when already logged in.

Connect/disconnect success and failure are flashed to the session and can be rendered with {{ session:success }} and {{ session:error }}.

Two-factor

OAuth sign-ins now respect two-factor authentication.

jasonvarga and others added 22 commits June 26, 2026 15:12
The direct-login branch set the oauth-provider session key (used to offer
OAuth re-auth on session expiry), but the 2FA challenge branch did not, so
2FA users lost that affordance. Set it before deferring to the challenge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A single shared disconnect route hardcoded the default guard, so a control
panel using a distinct cp guard would 401. Split it: the front-end route
authenticates via AuthGuard + auth (web guard), and the control panel route
lives in the cp routes where it inherits the cp guard and CSRF handling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jasonvarga jasonvarga requested a review from a team as a code owner June 30, 2026 22:04
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.

1 participant