Skip to content
This repository was archived by the owner on Oct 28, 2026. It is now read-only.

Make single sign-on able to stand alone, and fix Authentik account linking - #32

Merged
Zippybonzo merged 1 commit into
mainfrom
claude/trusting-keller-ljpawr
Sep 3, 2026
Merged

Zippybonzo merged 1 commit into
mainfrom
claude/trusting-keller-ljpawr

Conversation

@Zippybonzo

Copy link
Copy Markdown
Member

Follow-up to #31, from using it against a real Authentik instance. Three changes, all consequences of single sign-on becoming the primary way in rather than an alternative to the password form.

Password sign-in can be switched off

A new toggle on /settings/authentication, so an install with SSO working can insist on it.

Refused at the authentication callback rather than only hidden on the sign-in page — a form that is not rendered is not a closed door. Three things stop it being a way to lock yourself out of a console that deploys to production:

  • it cannot be switched off until SSO is actually usable (the API refuses, and says why);
  • it stops applying by itself if SSO is later disabled or left half-configured;
  • CONSOLE_FORCE_PASSWORD_LOGIN=true in the environment puts the form back for someone with a shell on the box — which is the position you are in on the day the provider is what broke, when the setting that would fix it is behind the sign-in page it is blocking.

The settings screen says which of those is in force when the checkbox and reality disagree.

Authentik could never link an existing account

The reported symptom was every sign-in ending in "The identity provider did not confirm your email address, so it cannot be linked to an existing account here." Two causes, both fixed.

email_verified read as two states instead of three. The claim is optional in the spec and Authentik does not send it, but it was read with filter_var(..., FILTER_VALIDATE_BOOLEAN), which cannot tell "said no" from "said nothing" — so every provider that omits it looked like one refusing to vouch for the address, and linking could never succeed. It is now read as the three states it really has, and a new setting — Trust email addresses the provider does not mark as verified, on by default — governs the absent case. An explicit email_verified: false is still refused whatever it is set to: that is the provider answering, and the answer stands. The two failures now also say which one happened, so the fix is one setting rather than a hunt through logs.

Userinfo was only consulted when the groups claim was missing. Authentik's default configuration puts a minimal set of claims in the ID token and the rest behind userinfo, so a token carrying groups but no email details was never topped up. The test is now per claim: anything the flow needs and the token lacks is fetched once, with the same sub match as before.

TOTP is no longer demanded of accounts the provider alone lets in

An account whose only way in is the provider has no password, so the provider is the sole gate and the second factor is the provider's to enforce. MFA enforcement for those accounts now lives entirely at the IdP — that is the trade this makes, and it is worth being explicit about.

An account that keeps a password alongside SSO is not exempt and must still enrol, because that password is a way in the provider never sees, so whatever MFA the provider enforces is not on that path.

Enrolment stays available to everyone, exempt or not. That needed a fix of its own: Fortify puts TOTP enrolment behind password confirmation, and a passwordless account has nothing to confirm — so the two-factor screen the account menu offers to all was a dead end for exactly the accounts SSO creates. App\Http\Middleware\ConfirmPassword lets an account with no password through and leaves every other account's confirmation unchanged.

Also

The sign-in page showed an OIDC failure twice — once from the guest layout's error summary and once from a block of its own.

Testing

318 tests pass (11 new), pint --test clean, npm run build clean. New coverage: password sign-in off and refused at the endpoint, the two lock-out guards and the environment override, a provider that sends no email_verified at all (both trust settings), an explicit false refused regardless, claims read from userinfo when the token omits them, the TOTP exemption and its password-holding boundary.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QjX5f2ZtAKYfiQmzWaD8tp


Generated by Claude Code

…king

Three things, all consequences of single sign-on becoming the primary way in
rather than an alternative to the password form.

**Password sign-in can be switched off.** A new toggle on the sign-in settings
screen, so an install with SSO working can insist on it. Refused at the
authentication callback rather than only hidden on the page — a form that is not
rendered is not a closed door. Three things stop it being a way to lock yourself
out: it cannot be switched off until SSO is usable, it stops applying by itself
if SSO is later disabled or left half-configured, and
CONSOLE_FORCE_PASSWORD_LOGIN=true in the environment puts the form back for
someone with a shell on the box, which is the position you are in on the day the
provider is what broke.

**Authentik could never link an existing account.** Two causes, both fixed.

`email_verified` is optional in the spec and Authentik does not send it, but the
claim was read with filter_var, which cannot tell "said no" from "said nothing":
every provider that omits it looked like one refusing to vouch for the address.
The claim is now read as the three states it really has, and a new setting —
"Trust email addresses the provider does not mark as verified", on by default —
governs the absent case. An explicit `email_verified: false` is still refused
whatever it is set to; that is the provider answering, and the answer stands.

Userinfo was also only consulted when the *groups* claim was missing, so a token
carrying groups but no email details was never topped up. Authentik's default
configuration puts a minimal set of claims in the ID token and the rest behind
userinfo, so the test is now per claim: anything the flow needs and the token
lacks is fetched once.

**TOTP is no longer demanded of accounts the provider alone lets in.** Those
accounts have no password, so the provider is the sole gate and the second factor
is the provider's to enforce. An account that keeps a password alongside SSO is
not exempt, because that password is a way in the provider never sees. Enrolment
stays available to everyone: our password-confirmation middleware lets a
passwordless account through, since Fortify's gate was otherwise a dead end for
the two-factor screen the account menu offers to all.

Also: the sign-in page showed the OIDC failure twice, once from the layout's
error summary and once from a block of its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjX5f2ZtAKYfiQmzWaD8tp
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 9 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: 2ac667be-423e-44ac-941a-a398a03b467c

📥 Commits

Reviewing files that changed from the base of the PR and between 7d363ec and 7179dd9.

📒 Files selected for processing (19)
  • README.md
  • app/Http/Controllers/Api/OidcSettingsController.php
  • app/Http/Controllers/Auth/OidcLoginController.php
  • app/Http/Middleware/ConfirmPassword.php
  • app/Http/Middleware/RequireTwoFactor.php
  • app/Models/OidcSettings.php
  • app/Models/User.php
  • app/Providers/FortifyServiceProvider.php
  • app/Services/Oidc/OidcIdentity.php
  • app/Services/Oidc/OidcUserProvisioner.php
  • bootstrap/app.php
  • config/console.php
  • database/migrations/2026_07_30_090000_add_password_login_and_email_trust_to_oidc_settings.php
  • docs/OPERATIONS.md
  • resources/js/console/pages/AuthenticationSettingsPage.vue
  • resources/views/auth/login.blade.php
  • tests/Feature/OidcLoginTest.php
  • tests/Feature/OidcSettingsTest.php
  • tests/Feature/TwoFactorEnforcementTest.php

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Zippybonzo
Zippybonzo merged commit bb8daae into main Sep 3, 2026
4 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants