One Authentik login. Twelve services. Zero per-service SSO configuration. The
sso.type:field in each package's manifest tells the framework which generic flow to run during bootstrap — the framework registers the OAuth app, configures redirects, populates the client id/secret, and the service joins the realm. No clicking through admin UIs.
Level: 4 · Reading time: 15 min
.env — search AUTHENTIK_ADMIN_PASSWORD).Already know this rung? Skip to Level 5 — backup and restore (planned) where you build the operational muscle for losing data without losing sleep.
PMA's SSO model: Authentik is the identity authority for everything. Every other service is a "client" that delegates login to Authentik. The user logs in once; the Authentik session unlocks every service.
This isn't ceremonial — it's encoded in each service's manifest. The bootstrap reads sso.type and dispatches to the right generic SSO flow. There's no per-service "wire up OAuth" script; six SSO protocols cover ~all services PMA bundles:
In this rung we'll: (1) verify SSO is live across services you already have, (2) add SSO to a service that doesn't have it, (3) fix a service whose SSO has drifted.
just sso-check
# Walks every service's manifest. For each with sso.type set,
# checks: Authentik has the application, redirect URIs match,
# client secret in .env matches Authentik's view.
Output should be all green. Any ❌ tells you exactly which
service drifted from its manifest.
# Or check one service specifically:
just sso-check redmine
Login flow verification — open any service's URL, click "Login",
you should be redirected to Authentik, log in once, redirected
back to the service. Open a second service in the same browser
session — no second login.
If you added a service following Level 2
without an sso: block, here's how to retrofit it.
Add to packages/<svc>/manifest.yaml:
sso:
type: oauth # or oidc / saml / proxy / wikijs / frappe
redirect_path: /login/oauth2/callback # service-specific, look at the service's docs
configured: false # set true after first successful configure
The sso.type values map to specific bootstrap flows:
sso.type |
Used by | What the flow does |
|---|---|---|
oauth |
Redmine, Mattermost, Linkding | Standard OAuth2 client — Authentik issues tokens via authorization code |
oidc |
Grafana, modern web apps | OpenID Connect — OAuth2 + identity claims in a JWT |
saml |
Older enterprise apps | SAML 2.0 — assertions signed by Authentik |
proxy |
Services without native SSO (e.g. internal admin tools) | Authentik runs as a forward-auth proxy in front of the service |
wikijs |
Wiki.js specifically | Wiki.js has a quirky OAuth flow that needs API-key bootstrapping |
frappe |
ERPNext / Frappe-based apps | Frappe has its own OAuth provider config format |
Then run the configure step:
just sso-add <svc>
# Creates the Authentik OAuth application + provider,
# registers redirect URIs, writes client id + secret to .env,
# updates the service's auth config accordingly.
Or run it as part of the next bootstrap — the framework does it
automatically when it sees configured: false.
Drift happens: someone manually changes the Authentik OAuth app,
or a .env regeneration creates a mismatch, or a tunnel hostname
changes and the registered redirect URI goes stale.
just sso-check <svc>
# Shows what's wrong — typically "redirect URI mismatch" or
# "client secret mismatch".
just sso-fix <svc>
# Re-applies the SSO config from the manifest, overwriting
# Authentik's view if needed.
For multiple services at once:
just sso-check # check all
just sso-fix-all # fix all (use with caution)
$ just sso-check
✓ redmine (oauth) — Authentik app present, redirects match
✓ mattermost (oauth) — Authentik app present, redirects match
✓ wikijs (wikijs) — Authentik app present, API key valid
✓ grafana (oidc) — Authentik app present, redirects match
✓ erpnext (frappe) — Authentik app present, client secret matches
✓ n8n (oidc) — Authentik app present, redirects match
✓ zammad (saml) — Authentik app present, signing cert matches
... (12 services, all green)
In the browser: open Redmine → log in via Authentik → close tab →
open Mattermost → click "Sign in with OIDC" → land authenticated.
Same for ERPNext, Wiki.js, Grafana, Superset, Zammad.
For each user added to Authentik, that user can immediately log
in to every PMA service that has sso.type set. Revoking a user
in Authentik locks them out of everything.
Three things to internalise:
sso.type is the contract. The framework dispatches on
this single field. Adding a new service that speaks OAuth?
Set sso.type: oauth, name the redirect path, you're done.
No new code in the framework, no new SSO script — the generic
OAuth flow already knows how to register apps in Authentik.
Per-service SSO flows live in scripts/sso/. Each
sso.type value maps to a script: setup-oauth.py,
setup-oidc.py, setup-saml.py, setup-proxy.py,
setup-wikijs.py, setup-frappe.py. They all do the same
abstract thing (register app, configure service auth) in the
protocol-specific way. Adding a 7th flow means adding one
script + one sso.type value; existing services don't change.
Authentik is the single point of identity. No service
stores user passwords (except the bootstrap admin used during
install). Adding a user to Authentik adds them to every PMA
service. Removing them removes them from every service. No
per-service user-management UI churn.
Reference: /pma/internals/architecture (planned) covers how
SSO fits into the nine-pillar framework. The SSO scripts live at
scripts/sso/setup-*.py; their shared helpers at scripts/sso/_lib.py.
sso.type: proxy (Authentik proxy provider sits in front).AUTHENTIK_URL in .env to the external instance + skip the bundled Authentik in your profile; bootstrap configures clients against the external one./pma/internals/mcp-gateway (planned) covers how AI agents authenticate via the same Authentik (with attribution per agent).