niklas‑stephan.de

Gramps Web at family.niklas-stephan.de with Authentik OIDC

Last updated 05/11/2026 · gramps, authentik, oidc, docker

Setup (verified 2026-10-25)

  • Compose /data/docker/family.niklas-stephan.de/: grampsweb (ghcr.io/gramps-project/grampsweb:latest, port 5000 via Traefik, certresolver letsencrypt-tls), grampsweb_celery (same image merged via YAML anchor, labels: [] so Traefik does not route it), grampsweb_redis (valkey:8-alpine).
  • OIDC env vars on the app service (prefix GRAMPSWEB_):
    • GRAMPSWEB_BASE_URL=https://family.niklas-stephan.de
    • GRAMPSWEB_OIDC_ENABLED=true
    • GRAMPSWEB_OIDC_ISSUER=https://auth.handtrixxx.com/application/o/gramps
    • GRAMPSWEB_OIDC_CLIENT_ID=gramps-web
    • GRAMPSWEB_OIDC_AUTO_REDIRECT=true
    • client secret in the project .env (mode 600, key GRAMPSWEB_OIDC_CLIENT_SECRET)
  • Authentik: application slug gramps, OAuth2Provider “Gramps Web”, client_id gramps-web, flows = the ones used by LiteLLM/pi-agent (“Authorize Application” / “Logged out of application”). Allowed redirect URIs must include both https://family.niklas-stephan.de/ and https://family.niklas-stephan.de/api/oidc/callback/custom - gramps-web redirects to the callback path, not the root.
  • Created provider via Django ORM (docker exec authentik-server python manage.py shell); redirect URIs need RedirectURI dataclass objects (from authentik.providers.oauth2.models import RedirectURI, RedirectURIType, RedirectURIMatchingMode), plain strings raise asdict() TypeError.
  • grant_types gotcha (hit 2026-10-25): a provider created via ORM has grant_types = [] (the UI pre-populates it), so authorize returns error=invalid_request (“The request is otherwise malformed”) back to the redirect_uri and gramps shows “OIDC authentication failed”. Set p.grant_types = ['authorization_code', 'refresh_token'] explicitly when creating via ORM.
  • JWKS/signing_key gotcha (hit 2026-10-25): without a signing_key on the OAuth2Provider, Authentik’s /application/o/gramps/jwks/ returns {} and signs ID tokens HS256 with the client secret; gramps-web’s authlib always verifies the ID token against the JWKS (KeyError: 'keys') -> “OIDC authentication failed for custom”. Fix: create a CertificateKeyPair (authentik.crypto.models; fields name, certificate_data, key_data) with a self-signed RSA cert and set provider.signing_key - then JWKS serves the RS256 key. Key pair stored in authentik DB as gramps-web-oidc-signing (created 2026-10-25, valid 10 years). Note: authentik ships cryptography 50, where CertificateBuilder is keyword-based (subject_name=, no .version()).
  • Scope mappings gotcha (hit 2026-10-25): ORM-created providers have no ScopeMapping links, so issued access tokens carry an empty scope; the userinfo endpoint then answers 403 (log: Scope mismatch. required: {'openid'}, token_has: set()) and gramps shows “OIDC authentication failed”. Fix: p.property_mappings.add(s) for the managed defaults named authentik default OAuth Mapping: OpenID 'openid'/'email'/'profile' (they live in ScopeMapping via the ProviderPropertyMapping through-model). UI-created providers get these automatically.
  • Roles (hit 2026-10-25): without OIDC_GROUP_* configured, every new OIDC account is created disabled (role -1) and shows “An administrator will review your account request” - even for the site owner. Fix: Authentik groups + GRAMPSWEB_OIDC_ROLE_CLAIM=groups and GRAMPSWEB_OIDC_GROUP_ADMIN=gramps-admin / ..._EDITOR=gramps-editor / ..._MEMBER=gramps-member (groups created in Authentik 2026-10-25; handtrixxx is in gramps-admin). The managed OpenID ‘profile’ scope mapping already emits a groups claim. Roles: OWNER=4, ADMIN=5, EDITOR=3, CONTRIBUTOR=2, MEMBER=1, GUEST=0, DISABLED=-1 (ADMIN is above OWNER). Fail-closed: a user in no mapped group becomes disabled. Role is re-evaluated on every login.
  • Two accounts exist: local handtrixxx (role 5, created with a password before OIDC) and the OIDC-created handtrixxx_1. Merge with the UI flow Settings -> “link SSO” (API: POST /api/oidc/link/ then /oidc/login/?provider=custom&link=<ticket>), or keep both and delete the spare via the admin user page.
  • Debug tip: gramps logs the actual authlib traceback (docker logs <grampsweb>); authentik logs Scope mismatch warnings. The flow API in 2026.x is POST /api/v3/flows/executor/<slug>/ (old /api/-/flow/ is gone).

Gotchas

  • Health endpoint is /health (not /api/health).
  • celery -A ... ping does not exist in the shipped celery version; celery healthcheck uses grep -q celery /proc/1/cmdline && </dev/tcp/grampsweb_redis/6379.
  • Login flow: GET /api/oidc/login/?provider=custom&tree=Familie -> 302 to Authentik. Without provider param it returns 422.
  • No local users yet (users table empty); first OIDC login creates the account. Role mapping configured 2026-10-25 via Authentik groups (see below).

Source: Wiki.js