Skip to content

SSO — Grafana login via Auth0 (shared with admin.tlsstress.art)

Grafana on obs.tlsstress.art uses the same Auth0 tenant as admin.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:

  1. You log into admin.tlsstress.art → Auth0 authenticates you (user/pass + MFA) and sets its SSO cookie.
  2. Later you open obs.tlsstress.art/grafana. Grafana (auto-login) redirects to Auth0 /authorize.
  3. Auth0 sees its active SSO cookie → returns an authorization code silently, no prompt.
  4. 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.)

  1. Auth0 Dashboard → Applications → Create Application
  2. Name: Grafana — Observability
  3. Type: Regular Web Application → Create.
  4. In the app's Settings:
  5. Allowed Callback URLs: https://obs.tlsstress.art/grafana/login/generic_oauth
  6. Allowed Logout URLs: https://obs.tlsstress.art/grafana/login
  7. Allowed Web Origins: https://obs.tlsstress.art
  8. Save.
  9. Copy Client ID and Client Secret.
  10. (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):

  1. 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 || []);
    };
    
  2. Set in .env / compose:
    GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH=contains(\"https://tlsstress.art/roles\"[*], 'observability-admin') && 'Admin' || 'Editor'
    
  3. 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)