SSO with OIDC (Keycloak)
Snitcher can accept sign-ins from an OIDC provider in addition to the built-in local admin, and sync
group membership from the token. The dev stack ships a ready-to-use Keycloak as an optional overlay —
bring it up with docker compose -f compose.yaml -f compose.keycloak.yaml up -d --build (it's off by
default; see deployments/dev/README.md).
How it works
- Snitcher validates bearer tokens from both issuers: the built-in local-admin issuer and the OIDC issuer. A policy scheme routes each request to the right validator by inspecting the token's issuer.
- On a valid OIDC token, Snitcher provisions the user (JIT) and syncs their groups from the
token's
groupsclaim, matching them to Snitcher DB groups by name. - Effective permissions then come from those groups (see Users & Groups).
Configuration
Enable it with these environment variables on the server:
| Variable | Example | Purpose |
|---|---|---|
Oidc__Enabled | true | Turn OIDC on. |
Oidc__MetadataAddress | http://keycloak:8080/realms/snitcher/.well-known/openid-configuration | Internal discovery URL the server uses to fetch metadata/JWKS. |
Oidc__Issuer | http://localhost:8081/realms/snitcher | External issuer the browser obtains tokens from (validated on the token). |
Oidc__Authority | http://localhost:8081/realms/snitcher | URL the SPA redirects to for login. |
Oidc__ClientId | snitcher-web | Public SPA client id. |
Oidc__ValidAudiences | snitcher-api (comma-separated for multiple) | Optional. Restricts accepted tokens to those whose aud claim matches. Off by default — see Audience validation below. |
The browser reaches Keycloak at localhost:8081, but the server reaches it at keycloak:8080. The
tokens carry the external issuer, so the server validates that issuer while fetching JWKS internally.
Keycloak's KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true bridges the two.
Audience validation
By default, Snitcher does not validate the token's aud (audience) claim for OIDC — only the
issuer and signature are checked. This is deliberate and IdP-agnostic: not every IdP stamps a
stable, predictable audience into its access tokens (Keycloak often uses account, Auth0 uses the
API identifier, Okta uses the auth-server audience), so validating it by default would break
out-of-the-box setups for many providers.
Security implication: with audience validation off, Snitcher accepts any valid token from the
trusted issuer, regardless of which application it was originally issued for. On a shared-realm
IdP — one issuer serving multiple applications/clients — a token minted for a different
application (but from the same realm/issuer) would also be accepted here. The server logs a loud
startup warning whenever OIDC is enabled and no Oidc__ValidAudiences is configured.
To restrict this: set Oidc__ValidAudiences to the audience value(s) your IdP puts in the
token's aud claim (comma-separated for multiple accepted values). This is strongly recommended
whenever your IdP realm is shared with other applications.
The bundled realm
deployments/dev/realm-snitcher.json is auto-imported by the dev stack and defines:
- Realm
snitcher, public clientsnitcher-web(Authorization Code + PKCE), with agroupsclaim mapper. - Groups Administrators / Operators / Viewers (names matching the seeded Snitcher groups).
- Test users
alice/alice(Operators) andbob/bob(Viewers).
The Keycloak admin console is at http://localhost:8081 (admin / admin).
Signing in
On the login page, choose Sign in with SSO. You're redirected to Keycloak, and after login the SPA
exchanges the code for a token and calls Snitcher. Try alice (can manage targets/Scouts/alerting, but
gets 403 on Groups) vs bob (read-only).
Verifying from the CLI
# Get a token for alice (Operators) via the password grant:
TOKEN=$(curl -s -X POST http://localhost:8081/realms/snitcher/protocol/openid-connect/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=password&client_id=snitcher-web&username=alice&password=alice&scope=openid' \
| sed -E 's/.*"access_token":"([^"]+)".*/\1/')
# Effective permissions (synced from her Operators group):
curl -s http://localhost:8080/api/Auth/me -H "Authorization: Bearer $TOKEN"
Using your own IdP
Point Oidc__* at any OIDC provider, ensure its tokens include a groups claim, and create Snitcher DB
groups whose names match the IdP group names you want to grant.