SSO — Grafana login via Auth0 (shared with admin.tlsstress.art)¶
Grafana on
obs.tlsstress.artuses the same Auth0 tenant asadmin.tlsstress.art. Log into either site and the other is released without re-authenticating. This is OIDC "generic OAuth" in Grafana + Auth0's tenant-wide SSO session.
1. Why this gives seamless SSO¶
Auth0 keeps a session cookie on the tenant domain
(dev-jddxc2f5cqlktay1.us.auth0.com). Every application in that tenant
(admin-console, Grafana, …) is a separate OIDC client, but they all ride the
same Auth0 session:
- You log into
admin.tlsstress.art→ Auth0 authenticates you (user/pass + MFA) and sets its SSO cookie. - Later you open
obs.tlsstress.art/grafana. Grafana (auto-login) redirects to Auth0/authorize. - Auth0 sees its active SSO cookie → returns an authorization code silently, no prompt.
- Grafana exchanges the code, reads
email/name, logs you in.
The reverse also holds (log into Grafana first → admin is silent next).
sequenceDiagram
actor U as Operator
participant ADM as admin.tlsstress.art
participant Z as Auth0 tenant
participant GF as Grafana (/grafana)
U->>ADM: login (user/pass + MFA)
ADM->>Z: OIDC authorize
Z-->>ADM: code → session (SSO cookie SET on tenant)
Note over U,GF: same browser, later
U->>GF: GET /grafana (auto_login)
GF->>Z: /authorize?client_id=GRAFANA
Z-->>GF: code (silent — SSO cookie present)
GF->>Z: POST /oauth/token (PKCE)
Z-->>GF: id_token + userinfo (email,name)
GF-->>U: authenticated, no prompt
2. One-time Auth0 dashboard step (operator)¶
Everything in Grafana is already wired; it activates when you create the Auth0
application and drop two values into .env. (This step needs the Auth0
dashboard — the project has no Auth0 Management API client to automate it.)
- Auth0 Dashboard → Applications → Create Application
- Name:
Grafana — Observability - Type: Regular Web Application → Create.
- In the app's Settings:
- Allowed Callback URLs:
https://obs.tlsstress.art/grafana/login/generic_oauth - Allowed Logout URLs:
https://obs.tlsstress.art/grafana/login - Allowed Web Origins:
https://obs.tlsstress.art - Save.
- Copy Client ID and Client Secret.
- (Optional, recommended) Connections tab → enable the same database/MFA connection used by admin so the same users apply.
3. Activate on the box¶
ssh -i ~/.ssh/tlsstress_f2_hetzner root@89.167.3.1
cd /opt/obs
# edit .env — set:
# GRAFANA_AUTH0_ENABLED=true
# GRAFANA_AUTH0_CLIENT_ID=<client id>
# GRAFANA_AUTH0_CLIENT_SECRET=<client secret>
# AUTH0_DOMAIN=dev-jddxc2f5cqlktay1.us.auth0.com # already defaulted
docker compose up -d grafana
Then open https://obs.tlsstress.art/grafana — you should bounce to Auth0 and
(if already logged into admin) come straight back in.
4. What's baked in Grafana (docker-compose.yml)¶
| Setting | Value |
|---|---|
GF_AUTH_GENERIC_OAUTH_ENABLED |
${GRAFANA_AUTH0_ENABLED:-false} |
GF_AUTH_GENERIC_OAUTH_AUTO_LOGIN |
${GRAFANA_AUTH0_ENABLED:-false} (bounce straight to Auth0) |
…_AUTH_URL |
https://$AUTH0_DOMAIN/authorize |
…_TOKEN_URL |
https://$AUTH0_DOMAIN/oauth/token |
…_API_URL |
https://$AUTH0_DOMAIN/userinfo |
…_SCOPES |
openid profile email |
…_USE_PKCE |
true |
…_EMAIL_ATTRIBUTE_PATH |
email |
…_ROLE_ATTRIBUTE_PATH |
'Admin' (constant — see role mapping) |
5. Role mapping¶
By default every authenticated user becomes a Grafana Admin
(ROLE_ATTRIBUTE_PATH: "'Admin'"). This is safe because the tenant already
gates who can log in — only provisioned admin identities reach Grafana at all.
To tighten later (map by an Auth0 roles claim):
- In Auth0 add a Login Action that injects roles into the token, e.g.
exports.onExecutePostLogin = async (event, api) => { const ns = "https://tlsstress.art/"; api.idToken.setCustomClaim(ns + "roles", event.authorization?.roles || []); }; - Set in
.env/ compose:GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH=contains(\"https://tlsstress.art/roles\"[*], 'observability-admin') && 'Admin' || 'Editor' docker compose up -d grafana.
GF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_GRAFANA_ADMIN=true lets the mapping grant
server-admin.
6. Break-glass (local admin)¶
The local Grafana admin login stays enabled even with auto-login on. If Auth0 is misconfigured, reach the password form at:
https://obs.tlsstress.art/grafana/login?disableAutoLogin
Username admin, password from /opt/obs/.env (GF_ADMIN_PASSWORD).
7. Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
| Redirect loop / "redirect_uri mismatch" | callback URL not whitelisted | add exact …/grafana/login/generic_oauth in Auth0 |
| Lands on login form, not Auth0 | GRAFANA_AUTH0_ENABLED not true |
set it + up -d grafana |
| "login attempt failed" after Auth0 | email claim missing | ensure email scope + verified email; …_EMAIL_ATTRIBUTE_PATH=email |
| Prompted for password every time | no shared session | confirm same tenant + same browser; check Auth0 "Seamless SSO"/session settings |
| Logged in but Viewer only | role mapping | review ROLE_ATTRIBUTE_PATH (default grants Admin) |