blumeops/docs/explanation/federated-login.md
Erich Blume cd50c1454a Integrate Forgejo with Authentik OIDC (#228)
## Summary

- Refactor Authentik blueprints: extract shared `admins` group into `common.yaml`, add `groups` scope mapping to all providers for group-based admin propagation
- Add Forgejo OAuth2 provider and application blueprint (`forgejo.yaml`)
- Add `forgejo-client-secret` to ExternalSecret and worker deployment env
- Configure Forgejo `[oauth2_client]` with `ACCOUNT_LINKING=login` to safely link existing accounts
- Update documentation (forgejo.md, authentik.md, federated-login.md)

## Deployment and Testing

After merge, deployment requires these steps in order:

1. **Authentik (ArgoCD):**
   - `argocd app set authentik --revision feature/forgejo-authentik-oidc && argocd app sync authentik`
   - Verify: Forgejo app/provider visible in Authentik admin UI
   - Verify: Grafana SSO still works (blueprint refactor)

2. **Forgejo app.ini (Ansible):**
   - `mise run provision-indri -- --tags forgejo --check --diff` (dry run)
   - `mise run provision-indri -- --tags forgejo` (apply, restarts Forgejo)

3. **Create Forgejo auth source (CLI on indri):**
   ```
   ssh indri 'sudo -u forgejo /opt/homebrew/bin/forgejo admin auth add-oauth \
     --name authentik \
     --provider openidConnect \
     --key forgejo \
     --secret "$(op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/Authentik (blumeops)/forgejo-client-secret")" \
     --auto-discover-url https://authentik.ops.eblu.me/application/o/forgejo/.well-known/openid-configuration \
     --scopes "openid email profile groups" \
     --group-claim-name groups \
     --admin-group admins'
   ```

4. **Link eblume account:** Sign in with Authentik on Forgejo, confirm link with local password

5. **Verify:** `tea repo list`, Forgejo Actions, local password break-glass

After merge: `argocd app set authentik --revision main && argocd app sync authentik`

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/228
2026-02-20 17:39:50 -08:00

4.3 KiB

title modified last-reviewed tags
Federated Login 2026-02-20 2026-02-20
explanation
security
oidc

Federated Login

Note: This article was drafted by AI and reviewed by Erich. I plan to rewrite all explanatory content in my own words - these serve as placeholders to establish the documentation structure.

How authentication works across BlumeOps services, and why it's designed this way.

The Problem

Without centralized authentication, every service manages its own users independently. Grafana has an admin password, ArgoCD has a different admin password, Forgejo has local accounts, and zot has no auth at all. This creates several problems:

  • Password sprawl — different credentials for every service, all stored separately in 1Password
  • No onboarding path — adding a collaborator means creating accounts in every service individually
  • No single sign-on — logging into Grafana doesn't help you access ArgoCD
  • Inconsistent security — some services have auth, some don't, and there's no central audit trail

The Solution: Authentik

BlumeOps uses authentik as the central OIDC identity provider. Authentik is the source of truth for user identity — users are created and managed in Authentik, and services authenticate against it via OpenID Connect.

This is a deliberate choice: Authentik provides a full-featured identity management UI, Blueprint-driven GitOps configuration, and support for multiple authentication protocols. Services like grafana delegate their login flow to Authentik using OIDC, and Authentik issues standardized tokens that carry user identity.

The Login Flow

When a user clicks "Sign in with Authentik" on Grafana:

1. Grafana redirects browser to Authentik   (authentik.ops.eblu.me/application/o/authorize/)
2. User logs in at Authentik                 (or is already logged in)
3. Authentik issues an OIDC token
4. Authentik redirects back to Grafana       (grafana.ops.eblu.me/login/generic_oauth)
5. Grafana accepts the token, user is logged in

If the user is already logged into Authentik, the flow happens instantly — it feels like a single click.

Break-Glass Access

Every service that uses Authentik SSO also keeps a local admin login. If Authentik goes down (or ringtail is offline), recovery works through:

  1. SSH to indri
  2. Log into ArgoCD with local admin password (from 1Password)
  3. Fix whatever is broken

Authentik is additive — it's a convenience layer, not a hard dependency. Services never lose their local auth capability.

Cross-Cluster Communication

Authentik runs on ringtail's k3s cluster while most services run on indri's minikube. This is deliberate — the IdP is independent of the main services cluster. Communication happens via the Tailscale network:

  • Grafana (minikube) → authentik.ops.eblu.me → Caddy (indri) → Tailscale → Authentik (ringtail k3s)
  • Browser redirects go through authentik.ops.eblu.me, resolved via Caddy

No k8s-internal DNS crosses cluster boundaries. Everything uses the *.ops.eblu.me domain.

Forgejo

forgejo authenticates against Authentik using the same OIDC flow as Grafana. The auth source is created via CLI (forgejo admin auth add-oauth) rather than config file — it lives in Forgejo's SQLite database.

Account linking is configured with ACCOUNT_LINKING = login: when an Authentik user's email matches an existing local account, Forgejo prompts for the local password to confirm the link. This safely preserves the existing eblume account with all its API tokens, SSH keys, and repository ownership.

The admins group in Authentik maps to Forgejo admin status, enabling centralized admin management.

MFA

Authentik enforces TOTP MFA on its default authentication flow (not_configured_action: configure). Forgejo's auth source has SkipLocalTwoFA: true, so SSO logins bypass Forgejo's local 2FA — Authentik has already verified the second factor. Local password logins (break-glass) still require Forgejo's own TOTP.

Future Work