# Authentik SSO Setup

Single Sign-On (SSO) for homelab services using Authentik.

## Overview

Authentik provides centralized authentication for all homelab services via OAuth2/OpenID Connect.

- **URL**: https://sso.vish.gg
- **Admin Interface**: https://sso.vish.gg/if/admin/
- **User Portal**: https://sso.vish.gg/if/user/
- **Host**: Calypso NAS (Synology DS723+)
- **Stack**: Docker Compose via Portainer

## Admin Credentials

- **Username**: `akadmin`
- **Email**: `admin@example.com`
- **Password**: REDACTED_PASSWORD in password manager

## Architecture

```
                    ┌──────────────────┐
                    │   Cloudflare     │
                    │   (DNS + SSL)    │
                    └────────┬─────────┘
                             │
                    ┌────────▼─────────┐
                    │  sso.vish.gg     │
                    │  (Authentik)     │
                    │  Calypso NAS     │
                    └────────┬─────────┘
                             │
        ┌────────────────────┼────────────────────┐
        │                    │                    │
        ▼                    ▼                    ▼
   ┌─────────┐         ┌─────────┐         ┌──────────┐
   │ Grafana │         │  Gitea  │         │Portainer │
   │gf.vish.gg│        │git.vish.gg│       │ internal │
   │homelab-vm│        │ Calypso │         │  Calypso │
   └─────────┘         └─────────┘         └──────────┘
```

## OAuth2 Providers

### Grafana

| Setting | Value |
|---------|-------|
| Client ID | `lEGw1UJ9Mhk6QVrNA61rAsr59Kel9gAvdPQ1FAJA` |
| Client Secret | `ArP5XWdkwVyw9nvXZaqjE9sIjXdmIgpgI4ZR8oKvTUVLgmIGVvKU8T867diMGSQXgTcWQQPbdbEdXTU1v3y9RKMnAqu2k6V4xlmxwNYlCDuk5inxJSdoC0V8ICtZxk1X` |
| Redirect URI | `https://gf.vish.gg` |
| Scopes | `openid profile email` |

**Configuration File**: `hosts/vms/homelab-vm/monitoring.yaml`

### Gitea

| Setting | Value |
|---------|-------|
| Client ID | `7KamS51a0H7V8HyIsfMKNJ8COstZEFh4Z8Em6ZhO` |
| Client Secret | `3IjyKCbHtgev6eMb1hYpQGHoGwPSRKda4ijRtbWfkhguNomxexxTiWtoWtyrXwGaF0ORj4D7D0kzB3Z1YN9DN5iz0HOKjAn5AdWJrSyxan02MjiwKmEriAbSGyh53uph` |
| Redirect URI | `https://git.vish.gg/user/oauth2/authentik/callback` |
| Discovery URL | `https://sso.vish.gg/application/o/gitea/.well-known/openid-configuration` |

**Configuration File**: `hosts/synology/calypso/gitea-server.yaml`

**Manual Setup Required**: Add OAuth2 source in Gitea admin UI:
1. Go to Site Administration → Authentication Sources
2. Add new OAuth2 source
3. Use Discovery URL for auto-configuration

### Portainer

| Setting | Value |
|---------|-------|
| Client ID | `fLLnVh8iUyJYdw5HKdt1Q7LHKJLLB8tLZwxmVhNs` |
| Client Secret | `xD9u47XbJd2g7vCeIyJC7MNvfEqytEnnHeVtJ7nU5Y1XGxYncXkejNAYkToUiRWcym3GpZIXgMpUnNNuUwud0Ff493ZwSHCiSKsk9n6RJLJ1iVvR20NdDnMe4YEGYXrt` |
| Redirect URI | `http://vishinator.synology.me:10000` |
| User Identifier | `email` |

**Configuration**: Via Portainer API (`/api/settings`)

### Reactive Resume v5

| Setting | Value |
|---------|-------|
| Client ID | `QU5qA7jLP9ghxy7iGMJoyZsCja2vY2Y2oGaLGjxA` |
| Client Secret | `wX1aFaby4aIABjLBBClYu4ukmIOjviL85GJBX8bAB3srQnt1BD31LcblRKyxzuv1yGwtsKLTFjwz12rUy6HknOqpIwk1QQ21jMjpWb1aa77iRG6lDkf4eNf8wWpE9Apo` |
| Redirect URI | `https://rx.vish.gg/api/auth/callback/custom` |
| Discovery URL | `https://sso.vish.gg/application/o/reactive-resume/.well-known/openid-configuration` |

**Configuration File**: `hosts/synology/calypso/reactive_resume_v5/docker-compose.yml` (also live at `/volume1/docker/rxv5/docker-compose.yml` on Calypso)

### Homarr

| Setting | Value |
|---------|-------|
| Client ID | `8oP0ha7gLjdz13MAPVsb7fe7TBkFBz7mt1eU8MEO` |
| Client Secret | `SpJXIGDk3SJfiS9GJwzH0fKrePsrumvCOmvFd2h0hEfxXMO77aCtpPEs6FShLTaUW5YxqgEDFkQi7q9NIOQDJTPQHlSy3nIeyDQmS2tVIV1BpSdGpnLQedouOkXACwe2` |
| Redirect URI | `https://dash.vish.gg/api/auth/callback/oidc` |
| Admin Group | `Homarr Admins` (Authentik group, pk=`892da833-5283-4672-a906-7448ae3ba9b6`) |
| Discovery URL | `https://sso.vish.gg/application/o/homarr/.well-known/openid-configuration` |

**Configuration File**: `hosts/synology/atlantis/homarr.yaml`

**Note**: `SECRET_ENCRYPTION_KEY` is required by Homarr — a 64-char hex key must be provided as an env var. The `AUTH_OIDC_ADMIN_GROUP` and `AUTH_OIDC_OWNER_GROUP` map to an Authentik group name.

### Immich

| Setting | Value |
|---------|-------|
| Client ID | `XSHhp1Hys1ZyRpbpGUv4iqu1y1kJXX7WIIFETqcL` |
| Client Secret | `mlbc4NbqiyRyUSqeUupaob7WsA3sURWExmoxYAcozClnmsdCPzGHlyO6zmErnS9YNyBsKOYoGUPvSTQPrE07UnYDLSMy286fycHoAJoc0cAN8BMc5cIif5kf88NSNCj2` |
| Redirect URIs | `http://192.168.0.250:8212/auth/login`, `http://calypso.vish.local:8212/auth/login`, `app.immich:/` |
| Issuer URL | `https://sso.vish.gg/application/o/immich/` |
| Button Text | `Sign in with Authentik` |
| Auto Register | true |

**Configuration**: Via `immich-config.json` mounted at `/config/immich-config.json` inside the container. Config file lives at `/volume1/docker/immich/config/immich-config.json` on Calypso and is tracked at `/home/homelab/immich-config.json`.

**Note**: Immich constructs the redirect URI dynamically from the hostname the browser used to access it — so every access hostname must be registered in Authentik. Currently registered: IP, `calypso.vish.local`, `app.immich:/`. `mobileRedirectUri` in the config file must be empty string — Immich's validator rejects custom URI schemes there.

### Headplane

| Setting | Value |
|---------|-------|
| Provider PK | `16` |
| Client ID | `1xLx9TkufvLGKgq8UmQV2RfTB6raSpEjZExBOhJ4` |
| Client Secret | `4r4n96jBGc8MlonyHStiN09ow0txTwERLupt9hsoNswpicEnJZHgKwi38jYP5zlou5J525dVFUmXNSvnxwBJgKIIAfpC43zi8yUVtT0NYNdEBeYQOsh1YW5jK8nVPSdc` |
| Redirect URI | `https://headscale.vish.gg:8443/admin/oidc/callback` |
| Issuer URL | `https://sso.vish.gg/application/o/headplane/` |
| Scopes | `openid profile email` |
| Sub Mode | `hashed_user_id` |

**Configuration File**: `hosts/synology/calypso/headplane-config.yaml` (reference, secrets redacted). Live config at `/volume1/docker/headscale/headplane/config.yaml` on Calypso.

**Note**: Headplane is served at `https://headscale.vish.gg:8443/admin` — no separate domain. NPM proxy host 44 routes `/admin` to port 3002. First user to log in via OIDC is automatically assigned the Owner role.

### NetBox

| Setting | Value |
|---------|-------|
| Provider PK | `23` |
| Client ID | `BB7PiOu8xFOl58H2MUfl9IHISVLuJ4UwwMGvmJ9N` |
| Client Secret | `CRdRVCM13JN9bSiT2aU74cFXSI9GpVBLBShOFGBpVHOQ4brnDWOzk8I02cEww8Gcrr6GnsU0XdBxHTEpfvX2u9rhmey7XDT3XUVVh9ADaSldww83hp4hAzH5eNx1zKvB` |
| Redirect URI | `https://nb.vish.gg/oauth/complete/oidc/` |
| Discovery URL | `https://sso.vish.gg/application/o/netbox/.well-known/openid-configuration` |
| Scopes | `openid profile email` |

**Configuration**: NetBox `configuration.py` on homelab-vm (`/home/homelab/docker/netbox/config/configuration.py`). Uses `python-social-auth` with `social_core.backends.open_id_connect.OpenIdConnectAuth` backend. `associate_by_email` pipeline maps Authentik users to existing NetBox accounts by email.

## Authentik Endpoints

| Endpoint | URL |
|----------|-----|
| Authorization | `https://sso.vish.gg/application/o/authorize/` |
| Token | `https://sso.vish.gg/application/o/token/` |
| User Info | `https://sso.vish.gg/application/o/userinfo/` |
| JWKS | `https://sso.vish.gg/application/o/{app-slug}/jwks/` |
| OpenID Config | `https://sso.vish.gg/application/o/{app-slug}/.well-known/openid-configuration` |
| End Session | `https://sso.vish.gg/application/o/{app-slug}/end-session/` |

## Docker Compose Configuration

**Location**: `hosts/synology/calypso/authentik.yaml`

Key environment variables:
- `AUTHENTIK_SECRET_KEY`: Random secret for encryption
- `AUTHENTIK_REDIS__HOST`: Redis container hostname
- `AUTHENTIK_POSTGRESQL__*`: PostgreSQL connection settings

## SSL/TLS Configuration

SSL is handled by Cloudflare Origin Certificate:
- Certificate ID: `lONWNn` (Synology reverse proxy)
- Covers: `*.vish.gg`
- Origin: Cloudflare Full (Strict) mode

## DNS Configuration

| Domain | Type | Target | Proxy |
|--------|------|--------|-------|
| sso.vish.gg | CNAME | calypso DDNS | Orange (proxied) |

## Adding New Services

### Method 1: OAuth2/OpenID (for apps that support it)

1. **Create Provider in Authentik**
   - Admin → Providers → Create → OAuth2/OpenID
   - Set name, redirect URIs, scopes

2. **Create Application**
   - Admin → Applications → Create
   - Link to provider
   - Set launch URL

3. **Configure Service**
   - Add OAuth2/OIDC settings to service config
   - Use Authentik endpoints
   - Test login flow

### Method 2: Proxy Provider (for apps without OAuth support)

Use this for apps like Actual Budget, Paperless-NGX, etc.

1. **Create Proxy Provider in Authentik**
   - Admin → Providers → Create → Proxy Provider
   - Name: e.g., "actual-proxy"
   - Authorization flow: default-provider-authorization-implicit-consent
   - External host: `https://actual.vish.gg`
   - Mode: Forward auth (single application)

2. **Create Application**
   - Admin → Applications → Create
   - Name: e.g., "Actual Budget"
   - Slug: `actual`
   - Provider: Select the proxy provider
   - Launch URL: `https://actual.vish.gg`

3. **Create Outpost** (if not exists)
   - Admin → Applications → Outposts
   - Create embedded outpost or deploy standalone
   - Add the application to the outpost

4. **Configure Nginx/Reverse Proxy**
   
   Add forward auth to your reverse proxy config:
   
   ```nginx
   location / {
       # Forward auth to Authentik
       auth_request /outpost.goauthentik.io/auth/nginx;
       error_page 401 = @goauthentik_proxy_signin;
       
       auth_request_set $auth_cookie $upstream_http_set_cookie;
       add_header Set-Cookie $auth_cookie;
       
       auth_request_set $authentik_username $upstream_http_x_authentik_username;
       auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
       auth_request_set $authentik_email $upstream_http_x_authentik_email;
       
       proxy_set_header X-authentik-username $authentik_username;
       proxy_set_header X-authentik-groups $authentik_groups;
       proxy_set_header X-authentik-email $authentik_email;
       
       # Your existing proxy_pass
       proxy_pass http://localhost:PORT;
   }
   
   location /outpost.goauthentik.io {
       proxy_pass https://sso.vish.gg/outpost.goauthentik.io;
       proxy_set_header Host $host;
       proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
   }
   
   location @goauthentik_proxy_signin {
       internal;
       add_header Set-Cookie $auth_cookie;
       return 302 /outpost.goauthentik.io/start?rd=$request_uri;
   }
   ```

### Bypassing Auth for Share Links

For services like Seafile that have share links:

```nginx
# Allow share links without auth
location /f/ {
    proxy_pass http://localhost:8611;
}

location /d/ {
    proxy_pass http://localhost:8611;
}

# Everything else requires auth
location / {
    auth_request /outpost.goauthentik.io/auth/nginx;
    # ... rest of auth config
    proxy_pass http://localhost:8611;
}
```

## Services Protection Summary

### OAuth2/OpenID Connect (Login Button)

Services with native OAuth support - users see a "Sign in with Authentik" button.

| Domain | Service | Backend | Port | Status |
|--------|---------|---------|------|--------|
| gf.vish.gg | Grafana | 192.168.0.210 | 3300 | ✅ Working |
| git.vish.gg | Gitea | 192.168.0.250 | 3052 | ✅ Working |
| sf.vish.gg | Seafile | 192.168.0.250 | 8611 | ✅ Working |
| vishinator.synology.me:10000 | Portainer | 192.168.0.250 | 9000 | ✅ Working |
| rx.vish.gg | Reactive Resume v5 | 192.168.0.250 | 4550 | ✅ Working |
| dash.vish.gg | Homarr | 192.168.0.200 | 7575 | ✅ Working |
| immich.vish.gg | Immich | 192.168.0.250 | 8212 | ✅ Working |
| headscale.vish.gg/admin | Headplane | 192.168.0.250 | 3002 | ✅ Working |
| nb.vish.gg | NetBox | 192.168.0.210 | 8443 | ✅ Working |

### Proxy Provider (Forward Auth)

Services without OAuth support - Authentik intercepts all requests and requires login first.

| Domain | Service | Backend | Port | Status |
|--------|---------|---------|------|--------|
| paperless.vish.gg | Paperless-NGX | 192.168.0.250 | 8777 | ✅ Working |
| docs.vish.gg | Paperless-NGX | 192.168.0.250 | 8777 | ✅ Working |
| actual.vish.gg | Actual Budget | 192.168.0.250 | 8304 | ✅ Working |
| npm.vish.gg | NPM Admin | 192.168.0.250 | 81 | ✅ Working |
| kuma.vish.gg | Uptime Kuma | 192.168.0.66 | 3001 | ✅ Working — `/status/*` public, rest gated |
| ollama.vish.gg | Ollama | 192.168.0.200 | 11434 | ✅ Working |
| wizarr.vish.gg | Wizarr | 192.168.0.200 | 5690 | ❌ Removed — caused redirect loop; Wizarr uses own auth |

### Services Without SSO

These services use their own authentication or are public.

| Domain | Service | Backend | Notes |
|--------|---------|---------|-------|
| sso.vish.gg | Authentik | 192.168.0.250:9000 | SSO itself |
| pw.vish.gg | Vaultwarden | 192.168.0.200:4080 | Own auth |
| ntfy.vish.gg | Ntfy | 192.168.0.210:8081 | Own auth |
| cal.vish.gg | Baikal | 192.168.0.200:12852 | CalDAV auth |
| dav.vish.gg | Seafile WebDAV | 192.168.0.250:8612 | WebDAV auth |
| mm.crista.love | Mattermost | 192.168.0.154:8065 | Own auth |
| mastodon.vish.gg | Mastodon | 192.168.0.154:3000 | Own auth |
| mx.vish.gg | Mail | 192.168.0.154:8082 | Own auth |
| ollama.vish.gg | Ollama | 192.168.0.200:11434 | See Forward Auth table above |
| retro.vish.gg | Retro Site | 192.168.0.250:8025 | Static site |
| rackula.vish.gg | Rackula | 192.168.0.250:3891 | Own auth |
| ost.vish.gg | OpenSpeedTest | 192.168.0.250:8004 | Public |

### Other Domains

| Domain | Service | Backend | Notes |
|--------|---------|---------|-------|
| hoarder.thevish.io | Hoarder | 192.168.0.210:3000 | Own auth |
| matrix.thevish.io | Matrix | 192.168.0.154:8081 | Own auth |
| joplin.thevish.io | Joplin Server | 192.168.0.200:22300 | Own auth |
| meet.thevish.io | Jitsi | 192.168.0.200:5443 | Public |
| binterest.thevish.io | Binternet | 192.168.0.210:21544 | Own auth |
| crista.love | Personal Site | 192.168.0.100:28888 | Static |
| rxv4access.vish.gg | Reactive Resume v4 | 192.168.0.250:9751 | STALE - 525 SSL error, dead instance |

## Troubleshooting

### OAuth Login Fails with "Unauthorized"
- Verify user has email set in Authentik
- Check redirect URI matches exactly
- Verify client secret is correct

### Certificate Errors
- Ensure Cloudflare proxy is enabled (orange cloud)
- Verify origin certificate is valid
- Check Synology reverse proxy SSL settings

### User Auto-Creation Not Working
- Enable "Auto Create Users" in service OAuth settings
- Verify email scope is requested
- Check user identifier matches (email/username)

## Recovery Access

If locked out of Authentik admin, you can create a recovery token:

```bash
# Via Portainer exec or SSH to Calypso
docker exec -it Authentik-SERVER ak create_recovery_key 10 akadmin
```

This generates a one-time recovery URL valid for 10 minutes.

## Related Documentation

- [Cloudflare Tunnels](./cloudflare-tunnels.md)
- [Port Forwarding Configuration](./port-forwarding-configuration.md)
- [Security](./security.md)
- [Grafana OAuth](../services/individual/grafana-oauth.md)
- [Gitea OAuth](../services/individual/gitea.md#-oauth2-single-sign-on-authentik)
- [Seafile OAuth](../services/individual/seafile-oauth.md)

## Change Log

- **2026-03-17**: Added NetBox OIDC provider (pk=23) — nb.vish.gg, associate_by_email pipeline
- **2026-03-17**: Removed Wizarr forward auth from NPM (wizarr has own auth, forward auth caused redirect loop)
- **2026-03-11**: Added Headplane OIDC provider (pk=16) — Headscale web UI at headscale.vish.gg/admin, port 3002
- **2026-03-08**: Added Forward Auth for Uptime Kuma (kuma.vish.gg), Ollama (ollama.vish.gg), Wizarr (wizarr.vish.gg)
- **2026-03-08**: Kuma /status/* and Wizarr /i/* paths are public; all other paths gated
- **2026-03-08**: Removed Forward Auth from dash.vish.gg NPM proxy (Homarr handles auth natively via OIDC)
- **2026-03-08**: Disabled Uptime Kuma built-in auth (disableAuth=true in SQLite); Authentik is sole gate
- **2026-03-08**: Calibre-Web started on port 8183 (8083 was occupied by Watchtower)
- **2026-03-08**: Added OIDC for Reactive Resume v5 (rx.vish.gg), Homarr (dash.vish.gg), Immich (immich.vish.gg) — all working
- **2026-03-08**: Fixed Homarr startup crash — SECRET_ENCRYPTION_KEY is mandatory (64-char hex)
- **2026-03-08**: Immich OAuth configured via immich-config.json mount (not Admin UI); mobileRedirectUri must be empty
- **2026-03-08**: Immich stack.env added to repo so stack is self-contained (no Portainer env injection needed)
- **2026-03-08**: Flagged rxv4access.vish.gg as stale (dead RR v4 instance, 525 SSL error)
- **2026-01-31**: Verified all OAuth2 and Forward Auth services working
- **2026-01-31**: Fixed Grafana OAuth "InternalError" - added scope mappings to provider
- **2026-01-31**: Removed Forward Auth from NPM for gf.vish.gg (conflicts with native OAuth)
- **2026-01-31**: Added scope mappings to Gitea, Portainer, Seafile OAuth2 providers
- **2026-01-31**: Updated comprehensive service protection summary
