Skip to content

OIDC + CF Access auth flow

PocketID es Identity Provider único del homelab. Dos rutas de auth según la app.

PocketID — 11 OIDC clients activos (2026-07-12)

PocketID (:1411 en VM 208) tiene 11 clientes OIDC registrados. La tabla lista los principales/validados E2E; el resto son clientes internos de otros servicios.

Cliente Hostname Estado E2E Notas
Karakeep bookmarks.monxas.casa ✅ Login E2E validado Auto-provisioning de usuarios
Grafana grafana.monxas.casa ✅ Login E2E validado Mapping role via groups
Paperless-ngx paperless.monxas.casa ✅ Login E2E validado OIDC social account
Vaultwarden pass.monxas.casa 🟡 Configurado, UI pendiente SSO config en admin
Immich pics.monxas.casa 🟡 Configurado, UI pendiente Auto-launch toggle
Audiobookshelf podcasts.monxas.casa 🟡 Configurado, UI pendiente OpenID Connect
Cloudflare Access monxas.cloudflareaccess.com ✅ Backing ~31 apps (no verificado) Sin email PIN

Ver Creating an OIDC client para el procedimiento de alta.

Apps con OIDC nativo (Karakeep, Grafana, Paperless...)

sequenceDiagram
    autonumber
    actor User
    participant CF as Cloudflare edge
    participant CFT as cloudflared (LXC 123)
    participant VIP as VIP .250 keepalived
    participant Caddy as Caddy LXC 270/271
    participant App as App (Grafana/Karakeep/Paperless)
    participant PID as PocketID

    User->>CF: GET https://grafana.monxas.casa
    CF->>CFT: Tunnel (HTTP/2)
    CFT->>VIP: https://192.168.0.250:443
    VIP->>Caddy: TCP (primary)
    Caddy->>App: reverse_proxy a VM 208:3030
    App-->>User: 302 /login (no session)

    User->>App: Click "Sign in with PocketID"
    App->>PID: 302 /authorize?client_id=X&PKCE
    PID-->>User: Login form (passkey o credenciales)
    User->>PID: Auth OK
    PID->>App: 302 /callback?code=AUTH
    App->>PID: POST /api/oidc/token (auth_code + secret)
    PID-->>App: access_token + id_token (JWT)
    App->>PID: GET /api/oidc/userinfo
    PID-->>App: {email, name, sub}
    App->>App: Session created (sub_id linked to local user via email)
    App-->>User: Logged in

Notas: - Token exchange usa client_secret_basic (HTTP Basic auth) o client_secret_post. PocketID acepta ambos. - PKCE obligatorio (pkce_enabled=1 en cliente PocketID). - Email link: si email del JWT matchea local user → linkea sin crear duplicado. Si no, crea user nuevo (puede requerir promoción manual a admin, ver guides/creating-oidc-client.md).

Apps con login propio + CF Access perimeter (~31 apps mgmt, no verificado)

sequenceDiagram
    autonumber
    actor User
    participant CF as Cloudflare edge
    participant CFA as CF Access policy
    participant PID as PocketID
    participant CFT as cloudflared
    participant App as App (Sonarr, Dashboard, Dockge...)

    User->>CF: GET https://sonarr.monxas.casa
    CF->>CFA: Check session cookie

    alt Sin sesión CF Access
        CFA->>PID: 302 /authorize (OIDC client "Cloudflare Access")
        PID-->>User: Login (passkey)
        User->>PID: Auth OK
        PID->>CFA: code → token
        CFA-->>User: Set session cookie (24h)
    end

    CFA->>CFT: Forward + headers JWT
    CFT->>App: HTTPS via tunnel
    App->>App: Auth own (Sonarr API key cookie, etc.)
    App-->>User: App UI (sesión propia)

Notas: - Sesión CF Access dura 24h, compartida entre las apps protegidas (~31, no verificado). - App sigue requiriendo su propio login (CF Access es perimeter, no SSO de app). - No email PIN — PocketID es único IdP en CF Zero Trust → Settings → Authentication.

Apps que SE QUEDAN FUERA de ambos

  • PocketID itself — sería loop infinito si se autoprotege.
  • monxas.casa apex — landing público.
  • cors, kiwix, loki, rag — públicas o bearer-token API.

Routing cross-cutting

graph LR
    subgraph "Internet"
        U[User]
    end

    subgraph "Cloudflare"
        E[CF edge]
        A[CF Access
31 apps] end subgraph "Homelab pmx-50" VIP{{VIP .250
keepalived}} C1[Caddy LXC 270
.247] VM[VM 208 backends
~75 containers] PID[PocketID
:1411] end subgraph "Homelab pmx-51" CFT[cloudflared
LXC 123] C2[Caddy LXC 271
.248] end U -->|HTTPS| E E -->|31 apps| A A -->|OIDC| PID E -->|tunnel| CFT CFT --> VIP VIP -.master.-> C1 VIP -.backup.-> C2 C1 --> VM C2 --> VM VM -->|reverse_proxy| PID style PID fill:#9c27b0,color:#fff style A fill:#ff6b6b,color:#fff style VIP fill:#ffd43b

Decisiones que importan

Decisión Por qué
PocketID en VM 208 Docker (no LXC propio) Stack ~/stacks/pocket-id/, DB SQLite local. Compatible con backups dockge habituales. Sin tunnel.* labels: NO se mete en routing automation (acceso via pocketid.monxas.casa por DNS + Caddy general rule).
CF Access NO detrás de Caddy LXC CF Access intercepta en edge antes de tunnel — Caddy nunca ve apps protegidas hasta que CF Access aprueba.
Caddy LXC full-routing (post-F4.f, 2026-05-20) LXC 270/271 son source-of-truth — importan managed.caddy (host:port targets a 192.168.0.208) + LE wildcard cert vía DNS-01. El container Caddy en VM 208 fue eliminado. Si VM 208 cae, backends caen pero el ingress (LXC 270/271) sigue respondiendo (puede servir error pages custom).
24h session CF Access + token PocketID stateless Balance UX (no re-login frecuente) vs security (limited blast radius si cookie leaked).