Authentik

OIDC identity provider running on the Bifrost VPS — GitHub SSO, ForwardAuth, and OIDC for Grafana and NetBird.

What is Authentik?

Authentik is a self-hosted identity provider (IdP) supporting OIDC, SAML, OAuth2, and LDAP. It allows centralizing authentication across multiple apps through a single SSO layer, with support for external OAuth sources like GitHub.

Why Authentik?

Authentik provides a fully self-hosted SSO with GitHub OAuth as the upstream identity source — no user passwords to manage. Its ForwardAuth outpost integrates directly with Traefik, gating every public service behind a session cookie without any per-app OAuth configuration.

How It's Used Here

Authentik runs on the Bifrost VPS (not inside the cluster) and handles three roles: GitHub OAuth login for all users, OIDC provider for Grafana and NetBird, and ForwardAuth middleware for Traefik to protect public cluster services. Managed by Pulumi's authentik stack (core/cloud/authentik.go, just core authentik up).


How It's Deployed

Authentik runs as two containers (authentik-server and authentik-worker) in docker-compose.yml on the Bifrost VPS, managed automatically by just core hetzner up.

Managed separately by Pulumi's authentik stack — configures OIDC apps, GitHub source, scopes, and ForwardAuth outpost against the live Authentik API:

just core authentik up

See Hetzner Bifrost for the VPS setup and container details.


Configuration

SettingValue
URLhttps://auth.madhan.app
Stackauthentik
Source filecore/cloud/authentik.go
GitHub OAuth ClientIDOv23liUPVh4nPuUJzGFp
Grafana OIDC ClientIDgrafana-homelab
NetBird OIDC ClientIDaumenijDycfG1cQURqH9BNJpV3KVUCoMHGPUVUlT

GitHub OAuth Source

Authentik's default-authentication-identification stage is configured to show a Login with GitHub button. Users log in with their GitHub account — Authentik creates a new user via the default-source-enrollment flow (identifier matching mode).

User → auth.madhan.app → "Login with GitHub" → GitHub OAuth → Authentik (new user enrolled)
  → OIDC token issued → redirect back to app (Grafana / NetBird)

Matching mode: identifier — Authentik creates a separate user per GitHub identity without trying to link to existing Authentik accounts by email. This avoids an infinite redirect loop that occurs with email_link mode when the admin email matches a GitHub account but the user isn't already logged into Authentik.

The GitHub OAuth app uses PKCE (S256) for additional security.


OIDC Applications

Grafana

Grafana uses Authentik as a confidential OIDC provider:

SettingValue
ClientIDgrafana-homelab
ClientSecretGRAFANA_OAUTH_CLIENT_SECRET (SOPS)
Redirect URIhttps://grafana.madhan.app/login/generic_oauth
Scopesopenid email profile groups offline_access
Token validity1 hour access, 10 min authorization code

The groups scope is a custom property mapping that returns the list of Authentik group names the user belongs to. Grafana uses this via role_attribute_path to assign the Admin role to members of the grafana-admins group.

# groups scope expression (in Authentik)
return [group.name for group in request.user.ak_groups.all()]
# Add a user to grafana-admins via Authentik UI → Directory → Groups → grafana-admins

NetBird

NetBird uses Authentik through its embedded Dex OIDC connector. Authentik acts as the upstream provider; Dex federates to it.

SettingValue
ClientIDaumenijDycfG1cQURqH9BNJpV3KVUCoMHGPUVUlT
ClientSecretNETBIRD_CLIENT_SECRET (SOPS)
Redirect URIshttps://netbird.madhan.app/oauth2/callback, http://localhost:53000
Scopesopenid email profile api offline_access

The api scope is a custom empty mapping required by NetBird's documentation.

Configure in NetBird: Settings → Identity Providers → Add → Authentik:

  • Issuer: https://auth.madhan.app/application/o/netbird/
  • Client ID: aumenijDycfG1cQURqH9BNJpV3KVUCoMHGPUVUlT
  • Client Secret: value of NETBIRD_CLIENT_SECRET from SOPS

ForwardAuth (Public Services)

Traefik on the Bifrost VPS uses Authentik's embedded outpost to protect public cluster services behind a session cookie.

How it works:

Browser → grafana.madhan.app
  → Traefik → authentik-forwardauth middleware
    → Authentik: is there a valid session cookie?
      No → redirect to auth.madhan.app → GitHub login
      Yes → forward to k8s-gateway (http://192.168.1.220)

The ForwardAuth provider uses forward_domain mode with cookie domain .madhan.app, so a single login covers all *.madhan.app subdomains. The embedded outpost is managed entirely in Pulumi — no manual outpost configuration needed.

Services with SkipAuth: true in cloudflare.go/publicServices (e.g. Grafana, which handles its own auth) bypass the ForwardAuth middleware.


NetBird Service Account

Authentik creates a sa-netbird service account in the authentik Admins group with a non-expiring API token. NetBird uses this token to sync users from Authentik.

# Get the service account token (Pulumi output):
cd core && pulumi stack select authentik && pulumi stack output NetbirdServiceToken --show-secrets

Secrets

All secrets are stored in secrets/bootstrap.sops.yaml and injected via sops exec-env during just core authentik up:

VariablePurpose
AUTHENTIK_TOKENBootstrap admin API token for Pulumi Authentik provider
AUTHENTIK_GITHUB_SECRETGitHub OAuth app client secret
GRAFANA_OAUTH_CLIENT_SECRETGrafana OIDC client secret
NETBIRD_CLIENT_SECRETNetBird Dex connector client secret

Troubleshooting

Grafana shows "Login with GitHub" but users get no roles

The user is not in the grafana-admins group. In Authentik: Directory → Groups → grafana-admins → Add user

ForwardAuth loops or redirects forever

# Check the embedded outpost is healthy
docker exec -n bifrost-net authentik-server ak healthcheck

# Verify outpost is registered in Authentik UI → Applications → Outposts

Re-running the Authentik stack

If Authentik resources drift or the stack errors, re-run:

just core authentik up

The stack is idempotent — it imports existing resources where possible.