feat(auth): per-instance opt-out for the email_verified strict check #115

Closed
opened 2026-06-18 13:58:57 +00:00 by james · 0 comments
Owner

Problem

Carol's OIDC flow requires payload.email_verified === true (after the userinfo fallback from #105) before accepting a sign-in. The check is the documented defence against the account-takeover-via-unverified-email vector enumerated in ADR-0015 §3 (inherited by ADR-0017 §3 threat #13).

For a self-hoster with complete control over their IdP's user population — Authentik administering a small internal team, OIDC tenant where the only path to user creation is admin-driven — the verified-email check is friction without security benefit. Every user that exists in the IdP exists because an operator put them there; "is this email verified?" reduces to "did the admin verify it?", which they already know.

There's no acceptable way to make this opt-out without making the trade-off explicit. The ticket is intentionally about how to make the opt-out hard to misuse, not just how to add a flag.

Scope

  • New optional env var: OIDC_<NAME>_REQUIRE_EMAIL_VERIFIED. Default true (current behaviour). Accepted opt-out value: literal string false only — anything else (including empty string, "no", "0", "off") falls back to true. Anti-typo defence.
  • Parse + plumb the flag through parseConfigsInstanceConfigbuildInstanceextractProfile in lib/auth/oidc-providers.ts.
  • In extractProfile's final check, when the flag is false:
    • Still require email to be present (Carol's linking decision tree needs it — see ADR-0015's no-auto-merge-by-email refusal paths, which still apply regardless of this flag).
    • Skip the emailVerified === true check.
    • Skip the userinfo fallback if it would only be needed for emailVerified (still call it if email itself is missing from id_token).
  • Log loudly when the flag is set: [oidc:<name>] email_verified check is DISABLED at instance-resolution time (not per-request — once per process, on startup-style discovery). Self-hosters reading container logs should see this without having to grep.
  • Tests:
    • tests/auth/oidc-providers.test.ts: env-parsing test that the flag round-trips on instance config.
    • tests/api/oauth.test.ts: end-to-end scenario — id_token with email_verified: false AND env opt-out → session created. Same id_token without the env opt-out (existing test) → oidc_claim redirect. Tests must cover both directions.
  • ADR-0017 amendment: new §4.6 documenting the opt-out, explicitly naming the threat it re-opens, the deployment shape it's intended for ("admin-curated user population, no self-service registration in the IdP"), and the deployment shape it's NOT intended for ("public IdP, social login, anything where attackers can register accounts").
  • docs/oidc-self-hoster-guide.md: new sub-section under "Troubleshooting" or as a sibling — labelled clearly as a foot-gun. Lead with the threat model, then the env var. Example:

    OIDC_<NAME>_REQUIRE_EMAIL_VERIFIED=false

    Disables the email_verified === true requirement on this instance's id_tokens. Set this only when you control the IdP's entire user population. If an attacker can register their own account in your IdP (open registration, social login, federation), they can use this to take over a Carol account with the same email.

Out of scope

  • Global (cross-instance) opt-out env var. Per-instance only; "this IdP is trusted" is a per-IdP statement.
  • Allowing email_verified of "true" (string) or 1 (number) to satisfy the strict check. That's a different change with a different threat model. The strict check is intentionally === true.
  • Removing the userinfo fallback. Still applies for the email claim itself.

Acceptance

  • A self-hoster sets OIDC_AUTHENTIK_REQUIRE_EMAIL_VERIFIED=false, restarts the container, and observes a single [oidc:authentik] email_verified check is DISABLED line in the startup-adjacent logs.
  • A user with email_verified: false in the id_token signs in successfully against that instance.
  • The same user signing in against a sibling instance with the env var unset / set to true (default) still gets ?error=oidc_claim.
  • ADR-0017 amendment makes the security trade-off greppable from docs/adr/.
## Problem Carol's OIDC flow requires `payload.email_verified === true` (after the userinfo fallback from #105) before accepting a sign-in. The check is the documented defence against the account-takeover-via-unverified-email vector enumerated in ADR-0015 §3 (inherited by ADR-0017 §3 threat #13). For a self-hoster with **complete control over their IdP's user population** — Authentik administering a small internal team, OIDC tenant where the only path to user creation is admin-driven — the verified-email check is friction without security benefit. Every user that exists in the IdP exists because an operator put them there; "is this email verified?" reduces to "did the admin verify it?", which they already know. There's no acceptable way to make this opt-out without making the trade-off explicit. The ticket is intentionally about *how to make the opt-out hard to misuse*, not just *how to add a flag*. ## Scope - [ ] New optional env var: `OIDC_<NAME>_REQUIRE_EMAIL_VERIFIED`. Default `true` (current behaviour). Accepted opt-out value: literal string `false` only — anything else (including empty string, "no", "0", "off") falls back to `true`. Anti-typo defence. - [ ] Parse + plumb the flag through `parseConfigs` → `InstanceConfig` → `buildInstance` → `extractProfile` in `lib/auth/oidc-providers.ts`. - [ ] In `extractProfile`'s final check, when the flag is `false`: - Still require `email` to be present (Carol's linking decision tree needs it — see ADR-0015's no-auto-merge-by-email refusal paths, which still apply regardless of this flag). - Skip the `emailVerified === true` check. - Skip the userinfo fallback if it would only be needed for `emailVerified` (still call it if `email` itself is missing from id_token). - [ ] Log loudly when the flag is set: `[oidc:<name>] email_verified check is DISABLED` at instance-resolution time (not per-request — once per process, on startup-style discovery). Self-hosters reading container logs should see this without having to grep. - [ ] Tests: - `tests/auth/oidc-providers.test.ts`: env-parsing test that the flag round-trips on instance config. - `tests/api/oauth.test.ts`: end-to-end scenario — id_token with `email_verified: false` AND env opt-out → session created. Same id_token without the env opt-out (existing test) → `oidc_claim` redirect. Tests must cover both directions. - [ ] ADR-0017 amendment: new §4.6 documenting the opt-out, explicitly naming the threat it re-opens, the deployment shape it's intended for ("admin-curated user population, no self-service registration in the IdP"), and the deployment shape it's NOT intended for ("public IdP, social login, anything where attackers can register accounts"). - [ ] `docs/oidc-self-hoster-guide.md`: new sub-section under "Troubleshooting" or as a sibling — labelled clearly as a foot-gun. Lead with the threat model, then the env var. Example: > **`OIDC_<NAME>_REQUIRE_EMAIL_VERIFIED=false`** > > Disables the `email_verified === true` requirement on this instance's id_tokens. **Set this only when you control the IdP's entire user population.** If an attacker can register their own account in your IdP (open registration, social login, federation), they can use this to take over a Carol account with the same email. ## Out of scope - Global (cross-instance) opt-out env var. Per-instance only; "this IdP is trusted" is a per-IdP statement. - Allowing `email_verified` of `"true"` (string) or `1` (number) to satisfy the strict check. That's a different change with a different threat model. The strict check is intentionally `=== true`. - Removing the userinfo fallback. Still applies for the `email` claim itself. ## Acceptance - A self-hoster sets `OIDC_AUTHENTIK_REQUIRE_EMAIL_VERIFIED=false`, restarts the container, and observes a single `[oidc:authentik] email_verified check is DISABLED` line in the startup-adjacent logs. - A user with `email_verified: false` in the id_token signs in successfully against that instance. - The same user signing in against a sibling instance with the env var unset / set to `true` (default) still gets `?error=oidc_claim`. - ADR-0017 amendment makes the security trade-off greppable from `docs/adr/`.
james closed this issue 2026-06-18 14:15:02 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
james/carol#115
No description provided.