Cloudflare Access + PocketID Authentication¶
How external services are protected. PocketID (pocketid.monxas.casa) is the
single OIDC identity provider; there are two distinct identity planes on top
of it.
Identity model: two planes¶
Both planes authenticate against the same PocketID IdP, but they sit in different places:
- Cloudflare Access (edge plane). At the Cloudflare edge, an Access
application challenges the request and delegates login to PocketID via the
OIDC client named
Cloudflare Access(PocketID acts as the IdP for CF Access). Used for services that have no authentication of their own. This plane leaves no trace inmanaged.caddy— it lives entirely in Cloudflare Zero Trust. - PocketID OIDC nativo (per-app plane). Apps that speak OIDC talk
directly to PocketID, each with its own OIDC client (Immich,
Paperless, Karakeep, Audiobookshelf, Grafana, Vaultwarden, Piso Colón,
Remote-Pulse Dashboard, …). This is real SSO inside the app — not
"built-in / own auth". No Cloudflare Access policy and no Caddy
forward_authare involved.
forward_auth no está en uso
managed.caddy no contiene ningún bloque forward_auth hoy
(2026-07-12). La auth no vive en Caddy: es OIDC nativo per-app + CF Access
en el borde.
Architecture Overview¶
User → *.monxas.casa (Cloudflare DNS)
→ Cloudflare Tunnel (cloudflared LXC on Proxmox, ID 123)
→ Cloudflare Access checks authentication
├── Not authenticated → Redirect to PocketID (pocketid.monxas.casa)
│ User logs in with passkey/password
│ PocketID returns OIDC token to Cloudflare
│ Cloudflare sets session cookie (24h)
└── Authenticated → Forward to origin service (192.168.0.208:PORT)
Components¶
Cloudflare Tunnel¶
- Location: Proxmox LXC container ID 123 (192.168.0.x)
- Tunnel name:
monxas-casa-tunnel - Tunnel ID:
d3d370db-f53c-4dcc-988e-880423e199d5 - Config: Runs with remote token (routes managed in Cloudflare dashboard)
- Service:
/usr/bin/cloudflared --no-autoupdate tunnel run --token <token> - Route config: Each Docker container uses labels to register with the tunnel:
PocketID (OIDC Provider)¶
- URL: https://pocketid.monxas.casa
- Stack:
/home/monxas/stacks/pocket-id/ - Container:
pocket-id-pocket-id-1on port 1411 - Image:
ghcr.io/pocket-id/pocket-id:v2 - Data:
/home/monxas/stacks/pocket-id/data/(SQLite DB + uploads) - Admin UI: https://pocketid.monxas.casa/settings/admin/oidc-clients
Cloudflare Access (Zero Trust)¶
- Dashboard: https://one.dash.cloudflare.com/ (or via API)
- Auth domain:
monxas.cloudflareaccess.com - Account ID:
0e70e9f3f8ffabbdcf9f46b0307a074e - PocketID IdP ID:
4c6842b9-cf65-42f3-89dc-69bf517c00c0
Cloudflare API Authentication¶
- Type: Global API Key
- Email: [email protected]
- Key: stored separately (37-character key)
- Usage:
-H "X-Auth-Key: <key>" -H "X-Auth-Email: [email protected]"
Current Protected Services¶
PocketID OIDC clients (11)¶
Estos son los clientes OIDC registrados en PocketID. Cloudflare Access es el
cliente del plano de borde; el resto son apps con OIDC nativo per-app.
| OIDC client | Plano |
|---|---|
| Cloudflare Access | Edge (CF Access → PocketID IdP) |
| Vaultwarden | OIDC nativo per-app |
| Karakeep | OIDC nativo per-app |
| Audiobookshelf | OIDC nativo per-app |
| Immich | OIDC nativo per-app |
| Paperless | OIDC nativo per-app |
| Grafana | OIDC nativo per-app |
| Remote-Pulse Dashboard | OIDC nativo per-app |
| wp-pulse | OIDC nativo per-app |
| wp-pulse-app | OIDC nativo per-app |
| Piso Colón | OIDC nativo per-app |
Protected by Cloudflare Access + PocketID (edge plane)¶
Apps sin auth propia; el login lo fuerza CF Access en el borde vía el cliente
OIDC Cloudflare Access.
| Service | Domain | Port |
|---|---|---|
| PairDrop | airdrop.monxas.casa | 3210 |
| PaddleOCR | ocr.monxas.casa | 8103 |
| Stirling PDF | pdf.monxas.casa | 8088 |
| Markdown Tool | md.monxas.casa | 8111 |
| Markdown API | md-api.monxas.casa | 8110 |
| ntfy | ntfy.monxas.casa | 8082 |
| Vera (OAP) | vera.monxas.casa | 8095 |
PocketID OIDC nativo (per-app plane)¶
Apps con SSO OIDC nativo contra PocketID (issuer pocketid.monxas.casa),
cada una con su propio OIDC client. No es «built-in / own auth» y no
llevan política de CF Access.
| Service | Domain | Port | OIDC client |
|---|---|---|---|
| Immich | pics.monxas.casa | 2283 | Immich |
| Paperless | paperless.monxas.casa | 8000 | Paperless |
| Karakeep | bookmarks.monxas.casa | 3333 | Karakeep |
| Audiobookshelf | podcasts.monxas.casa | 13378 | Audiobookshelf |
| Grafana | grafana.monxas.casa | 3030 | Grafana |
| Vaultwarden | pass.monxas.casa | 8055 | Vaultwarden |
| Piso Colón | piso.monxas.casa | — | Piso Colón |
| Remote-Pulse Dashboard | — | — | Remote-Pulse Dashboard |
Panel Piso Colón
piso.monxas.casa usa OIDC nativo PocketID (cliente Piso Colón,
issuer pocketid.monxas.casa), no auth propia.
Protected by own built-in auth (no PocketID, no Access policy)¶
Apps que siguen con su propia auth interna, sin integrar PocketID.
| Service | Domain | Port |
|---|---|---|
| Jellyfin | jellyfin.monxas.casa | 8096 |
| Jellyseerr | jellyseerr.monxas.casa | 5055 |
| Sonarr | sonarr.monxas.casa | 8989 |
| Radarr | radarr.monxas.casa | 7878 |
| Prowlarr | prowlarr.monxas.casa | 9696 |
| Bazarr | bazarr.monxas.casa | 6767 |
| Stash | nopor.monxas.casa | 9990 |
| Change Detection | changedetection.monxas.casa | 5000 |
Legacy Access policies (pre-PocketID, using OTP)¶
| Service | Domain |
|---|---|
| Dozzle | dozzle.monxas.casa |
| tunnel-auth | auth.monxas.casa |
How To: Add a New Service Behind PocketID¶
Step 1: Deploy the Docker container¶
In your docker-compose file, add the tunnel labels:
services:
my-service:
image: some-image:latest
restart: unless-stopped
ports:
- 8XXX:8080
labels:
- tunnel.hostname=myservice.monxas.casa
- tunnel.port=8XXX
Then start it:
Step 2: Add DNS record in Cloudflare¶
The tunnel should auto-create the DNS record, but if not, add a CNAME:
- Name: myservice
- Target: d3d370db-f53c-4dcc-988e-880423e199d5.cfargotunnel.com
- Proxy: ON (orange cloud)
Step 3: Add route in Cloudflare Tunnel¶
Go to Cloudflare Zero Trust dashboard → Networks → Tunnels → monxas-casa-tunnel → Public Hostname tab → Add:
- Subdomain: myservice
- Domain: monxas.casa
- Service: http://192.168.0.208:8XXX
Step 4: Create Cloudflare Access Application (via API)¶
ACCT="0e70e9f3f8ffabbdcf9f46b0307a074e"
IDP_ID="4c6842b9-cf65-42f3-89dc-69bf517c00c0"
cat > /tmp/cf_app.json << EOF
{
"name": "My Service",
"domain": "myservice.monxas.casa",
"type": "self_hosted",
"session_duration": "24h",
"allowed_idps": ["$IDP_ID"],
"auto_redirect_to_identity": true,
"policies": [
{
"name": "Allow authenticated users",
"decision": "allow",
"include": [{"everyone": {}}],
"precedence": 1
}
]
}
EOF
curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/${ACCT}/access/apps" \
-H "X-Auth-Key: <YOUR_API_KEY>" \
-H "X-Auth-Email: [email protected]" \
-H "Content-Type: application/json" \
-d @/tmp/cf_app.json | python3 -m json.tool
Step 5: Grant user access in PocketID¶
Go to https://pocketid.monxas.casa/settings/admin/oidc-clients → Cloudflare Access client → Allowed Users → make sure your user (or "all users") is allowed.
Note: You only need to do Step 5 once (the Cloudflare Access OIDC client already exists). New Access applications all go through the same single OIDC client.
How To: Remove Access Protection from a Service¶
ACCT="0e70e9f3f8ffabbdcf9f46b0307a074e"
APP_ID="<app-uuid>" # Get from listing below
curl -s -X DELETE "https://api.cloudflare.com/client/v4/accounts/${ACCT}/access/apps/${APP_ID}" \
-H "X-Auth-Key: <YOUR_API_KEY>" \
-H "X-Auth-Email: [email protected]" | python3 -m json.tool
How To: List All Access Applications¶
ACCT="0e70e9f3f8ffabbdcf9f46b0307a074e"
curl -s "https://api.cloudflare.com/client/v4/accounts/${ACCT}/access/apps" \
-H "X-Auth-Key: <YOUR_API_KEY>" \
-H "X-Auth-Email: [email protected]" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for app in data['result']:
print(f\"{app['name']:20s} {app.get('domain','N/A'):35s} {app['id']}\")
"
How To: Update PocketID OIDC Client¶
If you need to change the OIDC client settings (e.g., add scopes):
1. Go to https://pocketid.monxas.casa/settings/admin/oidc-clients
2. Click "Cloudflare Access"
3. Current settings:
- Client ID: 8c65bbe6-0c6e-4f07-b522-24f736fabf3d
- Callback URL: https://monxas.cloudflareaccess.com/cdn-cgi/access/callback
- Public client: OFF
- PKCE: ON
- Require re-auth: OFF
PocketID OIDC Endpoints¶
| Endpoint | URL |
|---|---|
| Discovery | https://pocketid.monxas.casa/.well-known/openid-configuration |
| Authorization | https://pocketid.monxas.casa/authorize |
| Token | https://pocketid.monxas.casa/api/oidc/token |
| JWKS | https://pocketid.monxas.casa/.well-known/jwks.json |
| Userinfo | https://pocketid.monxas.casa/api/oidc/userinfo |
Troubleshooting¶
"You're not allowed to access this service" (403)¶
Your PocketID user is not in the allowed users list for the OIDC client. → Go to PocketID admin → OIDC clients → Cloudflare Access → Allowed Users → add your user or set to "all users"
Auth loop / redirect fails¶
- Check PocketID logs:
docker logs pocket-id-pocket-id-1 --tail 50 - Verify callback URL matches:
https://monxas.cloudflareaccess.com/cdn-cgi/access/callback - Check the OIDC client ID matches in both PocketID and Cloudflare
Service not reachable after adding Access¶
- Verify tunnel route exists in Cloudflare dashboard
- Check DNS CNAME points to tunnel
- Test direct access:
curl http://192.168.0.208:PORT
PocketID is down¶
All Access-protected services will fail auth. Other services with built-in auth continue working.
cd /home/monxas/stacks/pocket-id && docker compose up -d
docker logs pocket-id-pocket-id-1 --tail 20
Reset PocketID completely¶
cd /home/monxas/stacks/pocket-id
docker compose down
rm -rf data/*
# Generate new encryption key
NEW_KEY=$(openssl rand -base64 32)
# Update .env with new ENCRYPTION_KEY=$NEW_KEY
docker compose up -d
# Visit https://pocketid.monxas.casa/setup to create admin account
# Then recreate the Cloudflare Access OIDC client and update Cloudflare IdP config
Backup & Recovery¶
PocketID data¶
- Database:
/home/monxas/stacks/pocket-id/data/pocket-id.db - Config:
/home/monxas/stacks/pocket-id/.env - Docker Compose:
/home/monxas/stacks/pocket-id/docker-compose.yml
Cloudflare Access config¶
Stored in Cloudflare's cloud. Can be recreated via API if needed. Key values to preserve:
- PocketID IdP ID: 4c6842b9-cf65-42f3-89dc-69bf517c00c0
- OIDC Client ID: 8c65bbe6-0c6e-4f07-b522-24f736fabf3d
- Account ID: 0e70e9f3f8ffabbdcf9f46b0307a074e
2026-06-01 — slicer protegido + token dedicado + limpieza¶
- Nueva app Access:
slicer.monxas.casa(OrcaSlicer) — self_hosted, IdP PocketID, policy allow-authenticated. app id7fa5cf8c-9ea3-4319-8ce6-7426b4b702c5. - Token dedicado Claude:
homelab-claude(user token, 53 chars). Scope: Access Apps&Policies (Edit), Access IdP (Read), Cloudflare Tunnel (Edit), Account Settings (Read) + DNS (Edit)/Zone (Read) en zonas. Guardado ensecrets/cloudflare.sops.yaml→cloudflare.claude_token. Revocable aislado. - Limpieza tokens (borrados):
NPM(DNS, sin uso desde 2024-07, era Nginx PM),Argo Tunnel monxas.casaduplicado sin usar,Cloudflare Tunnel iarquitectos.comduplicado sin usar. Quedan 4 activos: media-server (homelab-ctl, ya tiene scope Access completo), Edit zone DNS monxas.casa (Caddy ACME), iarquitectos tunnel, Argo monxas tunnel. - ⚠️ La Global API Key se usó/expuso en una sesión 2026-06-01 → ROTAR en dashboard (My Profile → API Tokens → Global API Key → Change).