Skip to main content

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 groups claim, 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:

VariableExamplePurpose
Oidc__EnabledtrueTurn OIDC on.
Oidc__MetadataAddresshttp://keycloak:8080/realms/snitcher/.well-known/openid-configurationInternal discovery URL the server uses to fetch metadata/JWKS.
Oidc__Issuerhttp://localhost:8081/realms/snitcherExternal issuer the browser obtains tokens from (validated on the token).
Oidc__Authorityhttp://localhost:8081/realms/snitcherURL the SPA redirects to for login.
Oidc__ClientIdsnitcher-webPublic SPA client id.
Oidc__ValidAudiencessnitcher-api (comma-separated for multiple)Optional. Restricts accepted tokens to those whose aud claim matches. Off by default — see Audience validation below.
Docker networking

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 client snitcher-web (Authorization Code + PKCE), with a groups claim mapper.
  • Groups Administrators / Operators / Viewers (names matching the seeded Snitcher groups).
  • Test users alice / alice (Operators) and bob / 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.