Fix Grafana Authentik SSO (500 + missing email claim) + RP landing test #54

Merged
dank merged 5 commits from fix/grafana-authentik-sso into main 2026-10-20 21:04:02 -07:00
Owner

What was broken

grafana.franksdank.cloud → "Sign in with Authentik" was dead, yet the e2e
suite passed green. 13-sso.spec.ts only proves an authenticated session
reaches Grafana's login page — it never follows the OAuth button, so the
relying-party (RP) login could be (and was) completely broken underneath. Four
independent breakages:

  1. Authentik grafana provider authorization_flow = NULL → every authorize
    request 500'd with AttributeError: 'NoneType' object has no attribute 'slug'
    ("Server Error — Powered by authentik"), before any claim was built.
  2. grafana-email-scope mapping returned the bare string user.email instead
    of a dict. authentik merges a scope-mapping result into the token as a
    mapping
    : a dict adds its keys as claims, a string is keyed under the mapping
    name — so no email claim was ever emitted. This had been mis-recorded in
    the vault as "the property-mapping evaluator is broken, userinfo returns only
    sub". It wasn't: the dict-valued grafana-profile-scope mapping always worked.
  3. Grafana requested scope user:email — a GitLab-ism authentik doesn't know,
    which also destabilised the authorize scope-overlap path.
  4. Grafana's API URL carried ?api=1 → Grafana v13 GETs
    <…>/userinfo/?api=1/emails for the address → 403/404 → "Login failed:
    InternalError".

The fix

  • IdP — scripts/authentik-grafana-sso-fix.sh (idempotent): set
    authorization_flow = default-provider-authorization-implicit-consent (the flow
    all other providers use) and make grafana-email-scope return
    {"email": user.email, "email_verified": True}. This is authentik DB state,
    not repo-deployable — hence a script that re-applies it after a provider
    re-creation or blueprint import (both revert it silently).
  • Grafana quadlet (grafana.container): SCOPES="openid email profile", a
    standard userinfo API_URL with no ?api=1, the
    email/sub/preferred_username attribute paths, and ALLOW_SIGN_UP=true
    (safe: the Anubis→Authentik guard fronts the callback, so it is unreachable to
    unauthenticated traffic).
  • Test — 20-grafana-sso.spec.ts (wired into the standard project): drives
    the real /login/generic_oauth Anubis+Authentik round-trip and asserts a live
    session (/api/user 200 with a non-empty email). This is the coverage that
    would have caught the outage; 13-sso cannot see it.
  • Docs — corrected Grafana.md + Authentik Outpost Reference.md; the
    "evaluator is broken / userinfo returns only sub / unverified" claims are
    destroyed and replaced with the verified causes.

Evidence

  • Reproduced the 500 and the email-claim gap live; fixed both on the IdP;
    re-verified: the RP login lands on / and /api/user → 200, user
    qa-e2e@localhost.local provisioned as Viewer (org 1, login = <sub hex>,
    isGrafanaAdmin:false).
  • Spec 20 passes against the live fleet (3.5 s). ./tests/run.sh green (secret
    seam and OKF conformance included).

The two IdP fixes are live on Dankclaw now (SSO works today). This branch is
what makes them reproducible — the repair script + docs land on main.


cwd: /home/blank/.omp/wt/wt-20261020-125416-6d1fc1f · session: wt-20261020-125416-6d1fc1f

## What was broken `grafana.franksdank.cloud` → "Sign in with Authentik" was **dead**, yet the e2e suite passed green. `13-sso.spec.ts` only proves an authenticated session reaches Grafana's *login page* — it never follows the OAuth button, so the relying-party (RP) login could be (and was) completely broken underneath. Four independent breakages: 1. **Authentik `grafana` provider `authorization_flow` = NULL** → every authorize request 500'd with `AttributeError: 'NoneType' object has no attribute 'slug'` ("Server Error — Powered by authentik"), before any claim was built. 2. **`grafana-email-scope` mapping returned the bare string `user.email`** instead of a dict. authentik merges a scope-mapping result into the token *as a mapping*: a dict adds its keys as claims, a string is keyed under the mapping name — so **no `email` claim was ever emitted**. This had been mis-recorded in the vault as "the property-mapping evaluator is broken, userinfo returns only sub". It wasn't: the dict-valued `grafana-profile-scope` mapping always worked. 3. **Grafana requested scope `user:email`** — a GitLab-ism authentik doesn't know, which also destabilised the authorize scope-overlap path. 4. **Grafana's API URL carried `?api=1`** → Grafana v13 GETs `<…>/userinfo/?api=1/emails` for the address → 403/404 → "Login failed: InternalError". ## The fix - **IdP** — `scripts/authentik-grafana-sso-fix.sh` (idempotent): set `authorization_flow = default-provider-authorization-implicit-consent` (the flow all other providers use) and make `grafana-email-scope` return `{"email": user.email, "email_verified": True}`. This is authentik **DB state**, not repo-deployable — hence a script that re-applies it after a provider re-creation or blueprint import (both revert it silently). - **Grafana quadlet** (`grafana.container`): `SCOPES="openid email profile"`, a standard userinfo `API_URL` with **no `?api=1`**, the `email`/`sub`/`preferred_username` attribute paths, and `ALLOW_SIGN_UP=true` (safe: the Anubis→Authentik guard fronts the callback, so it is unreachable to unauthenticated traffic). - **Test** — `20-grafana-sso.spec.ts` (wired into the `standard` project): drives the real `/login/generic_oauth` Anubis+Authentik round-trip and asserts a live session (`/api/user` 200 with a non-empty email). This is the coverage that would have caught the outage; 13-sso cannot see it. - **Docs** — corrected `Grafana.md` + `Authentik Outpost Reference.md`; the "evaluator is broken / userinfo returns only sub / unverified" claims are destroyed and replaced with the verified causes. ## Evidence - Reproduced the 500 and the email-claim gap live; fixed both on the IdP; re-verified: the RP login lands on `/` and `/api/user` → **200**, user `qa-e2e@localhost.local` provisioned as **Viewer** (org 1, `login = <sub hex>`, `isGrafanaAdmin:false`). - Spec 20 passes against the live fleet (3.5 s). `./tests/run.sh` green (secret seam and OKF conformance included). > The two IdP fixes are **live on Dankclaw now** (SSO works today). This branch is > what makes them *reproducible* — the repair script + docs land on main. --- cwd: /home/blank/.omp/wt/wt-20261020-125416-6d1fc1f · session: wt-20261020-125416-6d1fc1f
dank added 5 commits 2026-10-20 20:42:40 -07:00
The Authentik SSO button returned "Server Error" (500) because the grafana
provider authorization_flow was NULL and the authorize view crashed building a
None flow planner. After that, the login failed with "InternalError": no email
claim reached Grafana, so it fell back to GET <api_url>/emails, which authentik
404s.

Unit env: request authentik real scopes (openid email profile, not the GitLab
user:email that crashes the authorize view); read claims from the id_token
(USE_ID_TOKEN=true) and drop ?api=1 from the API URL (Grafana v13 turns it into
a /emails 404); pin email/sub/preferred_username attribute paths; allow
first-login provisioning since the Anubis->Authentik guard fronts the callback.

scripts/authentik-grafana-sso-fix.sh idempotently re-applies the two IdP-side
causes (NULL authorization_flow; the grafana-email-scope mapping that returned
a bare string instead of {"email":..}). Verified end to end: RP login lands on /
and /api/user answers 200 with the real email, user provisioned as Viewer.
13-sso proves an authenticated session reaches Grafana login page but never
follows the OAuth button, so the relying-party login could be dead (500, or an
email-claim gap bouncing to /login) while the suite stayed green. Spec 20 drives
/login/generic_oauth through the real Anubis+authentik round-trip and asserts a
live session (/api/user 200 with a non-empty email). Wire 20- into the standard
project (qa-e2e session).
The vault claimed the fork property-mapping evaluator was broken and userinfo
returned only sub, so email could never reach Grafana (outcome unverified). That
was a misdiagnosis: the real causes were the NULL authorization_flow (the
NoneType.slug crash) and the email scope mapping returning a bare string rather
than a dict. Record the four verified causes and fixes, point to the repair
script and the new landing spec, and drop the unverified caveat.
The Authentik SSO button returned "Server Error" (500): the grafana provider had
a NULL authorization_flow and the authorize view crashed building a None flow
planner. After that, the login failed with "InternalError" — no email claim
reached Grafana, so it fell back to GET <api_url>/emails, which authentik 404s.

Unit env: request authentik real scopes (openid email profile, not the GitLab
user:email that crashes the authorize view); standard userinfo API_URL with no
?api=1 (Grafana v13 turns ?api=1 into a /emails 403); email/sub/preferred_username
attribute paths; ALLOW_SIGN_UP=true since the Anubis->Authentik guard fronts the
callback. scripts/authentik-grafana-sso-fix.sh idempotently re-applies the two
IdP-side causes (NULL authorization_flow; grafana-email-scope returning a bare
string instead of {"email":..}). Verified end to end: RP login lands on / and
/api/user answers 200 with the real email, user provisioned as Viewer.
The login lands once authentik emits the email claim (the IdP fix); Grafana needs
no USE_ID_TOKEN toggle, and dropping it also avoids a *_TOKEN false-positive in
the literal-secret seam. Correct the two SSO notes accordingly.
dank merged commit 7d85fed1c9 into main 2026-10-20 21:04:02 -07:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dank/serverAppConfigs#54