Minimal OIDC `forward_auth` gateway for homelab apps behind Caddy, with Pocket-ID as the IdP.
819
Minimal OIDC forward_auth gateway for homelab apps behind Caddy, with Pocket-ID as the IdP.
GET /verify hot path for Caddy auth checks (local checks only, no outbound I/O).GET /login starts OIDC Authorization Code flow.GET /callback exchanges code, validates ID token, creates in-memory session.GET|POST /logout clears session cookie + RAM session.GET /healthz liveness endpoint.--healthcheck CLI mode for container health probes (no curl/wget required)./verify via Auth-Gateway-Secret.default_policy + host_policies).kid miss.enable_pkce, default true).cargo build --release
Binary:
target/release/pocket-shield
Default config path is config.yaml unless you pass --config or POCKET_SHIELD_CONFIG.
POCKET_SHIELD_CONFIG=./config.toml cargo run
or
./target/release/pocket-shield --config ./config.toml
Use the binary healthcheck mode to probe GET /healthz from minimal images:
./target/release/pocket-shield --config ./config.toml --healthcheck
Exit code is 0 on success and non-zero on failure.
For wildcard binds, healthcheck uses loopback automatically:
0.0.0.0:8080 -> http://127.0.0.1:8080/healthz[::]:8080 -> http://[::1]:8080/healthzDocker/Podman example:
HEALTHCHECK CMD ["/app/pocket-shield", "--config", "/etc/pocket-shield/config.toml", "--healthcheck"]
config.example.toml.issuer and gateway.public_base_url must be https://.gateway.caddy_shared_secret_file and client secret file paths must exist and contain non-empty secrets.public_base_url + /callback.rd, safe_default_rd, logout_redirect) are restricted to gateway.allowed_redirect_root_domain.mode = "single": use [single_client] for every host.mode = "multi": use [[multi_clients]] keyed by host.default_policy.allowed_groups: fallback groups for hosts without overrides.[[host_policies]]: per-host allowed groups.GET /verifyExpected request headers (from Caddy):
Auth-Gateway-SecretX-Forwarded-HostX-Forwarded-ProtoX-Forwarded-UriBehavior:
403 forbidden302 redirect to /login?rd=<original-url>403 forbidden204 No Content with identity headers:
remote-userremote-email (if present)remote-name (if present)remote-groups (comma-separated)GET /login?rd=<https-url>: validates redirect target and redirects to IdP authorize endpoint.GET /callback?code=...&state=...: validates login state, exchanges code, validates ID token, sets session cookie, redirects to original rd.GET|POST /logout: removes session and clears cookie, then redirects to configured logout destination.GET /healthz: returns 200 ok.forward_auth at http://pocket-shield:8080/verify (or equivalent internal address).Auth-Gateway-Secret.remote-* response headers from auth check into upstream request headers.forward_auth to Pocket-ID endpoints or gateway login/callback/logout endpoints.{
# Store this in your environment, not inline in Caddyfile.
# AUTH_GATEWAY_SECRET=...
}
auth.home.example.com {
# Gateway endpoints
@gateway path /verify /login /callback /logout /healthz
reverse_proxy @gateway pocket-shield:8080
# Pocket-ID endpoints
@pocketid path /.well-known/openid-configuration /authorize /token /jwks
reverse_proxy @pocketid pocket-id:1411
}
paperless.home.example.com {
# Prevent client header spoofing.
header {
-Remote-User
-Remote-Email
-Remote-Name
-Remote-Groups
}
forward_auth pocket-shield:8080 {
uri /verify
copy_headers Remote-User Remote-Email Remote-Name Remote-Groups
header_up Auth-Gateway-Secret {env.AUTH_GATEWAY_SECRET}
}
reverse_proxy paperless:8000
}
Secure, HttpOnly, SameSite=Lax, domain-scoped via gateway.cookie_domain.Licensed under the MIT License.
Content type
Image
Digest
sha256:f1f7cee1b…
Size
14.1 MB
Last updated
6 months ago
docker pull pgilad/pocket-shield:2026.04.25.123345