OAuth / OIDC Single Sign-On
PuppyGraph supports single sign-on (SSO) through any OpenID Connect (OIDC) identity provider (IdP), including Okta, Auth0, Google Workspace, Azure AD / Microsoft Entra ID, and Keycloak. With SSO enabled, users authenticate against your IdP and their PuppyGraph role can be assigned automatically from IdP groups (see Role-Based Access Control).
The login flow is the standard OIDC Authorization Code flow with PKCE: PuppyGraph redirects the browser to your IdP, exchanges the returned authorization code for an ID token, and verifies the token's signature, issuer, audience, and nonce. The configured SSO_CLAIM_AS_USER_ID claim becomes the user's identity (prefixed sso:); the configured groups claim drives RBAC role assignment.
Want a runnable demo first?
Two step-by-step tutorials walk through the flow against a local Keycloak so you can try it without a production identity provider:
- Setting Up SSO with Keycloak. Keycloak + PuppyGraph, just the SSO login flow.
- Setting Up Row-Level Security with Keycloak. Adds Postgres-backed row-level security on top.
The rest of this page is the configuration reference you'll need when wiring your own IdP.
Configuration
Set the following environment variables on the control plane (the node started with CONTROLPLANE=true, or the controlplane service in a multi-service compose file). SSO request handling lives only on the control plane. Leader and compute nodes do not need the SSO_* variables, but they still need the cluster-wide auth settings (RBAC_ENABLED, GREMLINSERVER_AUTHENTICATION_ENABLED, BOLTSERVER_AUTHENTICATION_ENABLED, AUTHENTICATION_JWT_SECRETKEY) so JWTs minted by the control plane validate on the query path.
Required variables
| Variable | Description |
|---|---|
SSO_ENABLED |
Set to true to turn SSO on. |
SSO_CLIENT_ID |
Client ID issued by your IdP. |
SSO_CLIENT_SECRET |
Client secret issued by your IdP. Treat as a secret. |
SSO_ISSUER |
The IdP's issuer URL (e.g. https://your-tenant.okta.com/oauth2/default). Used to verify the ID token's iss claim. |
SSO_CALLBACK_URL |
The publicly reachable callback URL, e.g. https://puppy.example.com/sso_callback. Must exactly match the redirect URI registered with the IdP. |
When registering PuppyGraph in your IdP, set the redirect URI to <your-puppygraph-base-url>/sso_callback, allow the authorization_code grant, and authorize the openid, profile, and email scopes (plus your provider's groups scope if you plan to use group mapping).
Endpoint configuration
PuppyGraph does not auto-discover endpoints from the OIDC discovery document. There are exactly two ways for PuppyGraph to know where the authorization and token endpoints are:
- Okta-style fallback. If
SSO_URLandSSO_ACCESS_TOKEN_URLare unset, PuppyGraph constructs them fromSSO_ISSUERusing Okta's path convention: - Authorization endpoint:
<SSO_ISSUER>/v1/authorize - Token endpoint:
<SSO_ISSUER>/v1/token
This fallback works for Okta and other providers that happen to follow the same URL shape. It does not work for Auth0, Google, Azure AD, Keycloak, etc.
- Explicit URLs. For every other provider, set the endpoint URLs explicitly:
| Variable | Description |
|---|---|
SSO_URL |
Authorization endpoint URL (where the browser is redirected to log in). |
SSO_ACCESS_TOKEN_URL |
Token endpoint URL (used server-to-server to exchange the authorization code). |
SSO_USERINFO_URL |
Userinfo endpoint URL. Optional; consulted only when SSO_CLAIM_AS_USER_ID is missing from the ID token. Other claims (including the groups claim used for role mapping) are read from the ID token, never from userinfo. |
SSO_JWKS_URL |
JWKS endpoint URL for ID token signature verification. If unset, PuppyGraph falls back to the default Okta-style verifier driven by SSO_ISSUER and SSO_CLIENT_ID. |
Look these URLs up in your IdP's admin console or its <issuer>/.well-known/openid-configuration document.
Optional variables
| Variable | Default | Description |
|---|---|---|
SSO_SCOPE |
openid profile email |
Space-separated OAuth scopes. Add your provider's groups scope (e.g. openid profile email groups) if you use group mapping. |
SSO_CLAIM_AS_USER_ID |
email |
JWT claim that becomes the PuppyGraph user identifier. Typical values: email, preferred_username, sub. |
SSO_GROUPS_CLAIM |
groups |
JWT claim containing the user's group memberships. Used for group-based role mapping. |
SSO_SESSION_NAME |
sso_session |
Cookie name for the SSO session. |
SSO_RESPONSE_TYPE |
code |
OIDC response type. Only code is supported. |
SSO_HTTP_TIMEOUT |
10s |
Timeout for outbound HTTP calls to the IdP. |
SSO_JWKS_FETCH_TIMEOUT |
10s |
Timeout for fetching the JWKS document. |
SSO_JWKS_CACHE_TTL |
5m |
How long PuppyGraph caches the JWKS document. |
Provider recipes
The values below are templates; substitute your tenant ID, domain, and client credentials. For Keycloak, the Setting Up SSO with Keycloak tutorial is a fully worked example.
Okta
SSO_ENABLED=true
SSO_CLIENT_ID=0oa...example
SSO_CLIENT_SECRET=...
SSO_ISSUER=https://your-tenant.okta.com/oauth2/default
SSO_CALLBACK_URL=https://puppy.example.com/sso_callback
SSO_SCOPE="openid profile email groups"
SSO_CLAIM_AS_USER_ID=email
SSO_GROUPS_CLAIM=groups
Add a Groups claim to the Okta authorization server so the groups claim is included in the ID token. Endpoints are derived from the issuer automatically.
Auth0
SSO_ENABLED=true
SSO_CLIENT_ID=...
SSO_CLIENT_SECRET=...
SSO_ISSUER=https://your-tenant.auth0.com/
SSO_URL=https://your-tenant.auth0.com/authorize
SSO_ACCESS_TOKEN_URL=https://your-tenant.auth0.com/oauth/token
SSO_JWKS_URL=https://your-tenant.auth0.com/.well-known/jwks.json
SSO_CALLBACK_URL=https://puppy.example.com/sso_callback
SSO_CLAIM_AS_USER_ID=email
Auth0 does not include groups on the ID token by default. Add an Action or Rule that copies app_metadata.groups (or your custom claim) onto the ID token under the claim name you set in SSO_GROUPS_CLAIM.
Google Workspace
SSO_ENABLED=true
SSO_CLIENT_ID=...apps.googleusercontent.com
SSO_CLIENT_SECRET=...
SSO_ISSUER=https://accounts.google.com
SSO_URL=https://accounts.google.com/o/oauth2/v2/auth
SSO_ACCESS_TOKEN_URL=https://oauth2.googleapis.com/token
SSO_JWKS_URL=https://www.googleapis.com/oauth2/v3/certs
SSO_CALLBACK_URL=https://puppy.example.com/sso_callback
SSO_CLAIM_AS_USER_ID=email
Google does not emit a groups claim on the ID token. Use explicit role assignment per user in Settings → Users, or proxy groups through a custom claim provider.
Azure AD / Microsoft Entra ID
SSO_ENABLED=true
SSO_CLIENT_ID=...
SSO_CLIENT_SECRET=...
SSO_ISSUER=https://login.microsoftonline.com/<tenant-id>/v2.0
SSO_URL=https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
SSO_ACCESS_TOKEN_URL=https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
SSO_JWKS_URL=https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys
SSO_CALLBACK_URL=https://puppy.example.com/sso_callback
SSO_CLAIM_AS_USER_ID=preferred_username
SSO_GROUPS_CLAIM=groups
In the Azure AD application registration, configure the optional groups claim under Token configuration so group object IDs (or names) appear on the ID token.
Relationship to RBAC
SSO governs authentication (who you are). Role-Based Access Control governs authorization (what you can do). After a successful SSO login, the user's role is determined in this order:
- Explicit role assignment by an admin in Settings → Users.
- Group mapping based on the IdP's groups claim (see Configuring SSO group mappings).
- Default role from
RBAC_DEFAULT_ROLE(defaults toAnalyst).
SSO users always appear in the Users tab with a sso: prefix on their internal identifier so they cannot collide with local users.
Troubleshooting
| Symptom | Likely cause | What to check |
|---|---|---|
Browser shows redirect_uri_mismatch |
The IdP's registered redirect URI does not match SSO_CALLBACK_URL exactly. |
Confirm the scheme, host, port, and path match. The path must be /sso_callback. |
SSO authorization endpoint is not configured error |
SSO_ISSUER is empty and SSO_URL is not set. |
Set SSO_ISSUER for issuers with the Okta URL convention, or set SSO_URL explicitly. |
invalid issuer: … after IdP redirect |
The iss claim in the ID token does not match SSO_ISSUER. |
Inspect the ID token at the IdP and copy the exact issuer value (trailing slash matters for some providers). |
| Token verification fails | Signing key cannot be found. | Set SSO_JWKS_URL to your IdP's JWKS endpoint. Confirm the JWKS document is reachable from PuppyGraph. |
| Users log in but always get the default role | Groups claim missing from the ID token or under a different name. | PuppyGraph reads groups from the ID token claims (not from the userinfo endpoint). Confirm the IdP includes the groups claim on the ID token and that SSO_GROUPS_CLAIM matches the claim name. |
Users log in with the wrong identifier (e.g. an opaque sub) |
SSO_CLAIM_AS_USER_ID does not match the claim you want as the username. |
Set SSO_CLAIM_AS_USER_ID to email, preferred_username, or whichever claim your IdP exposes for end-user identity. |