Setting Up SSO with Keycloak
Summary
In this tutorial, you will:
- Start a Keycloak container preseeded with an OIDC client and a test user.
- Start a PuppyGraph container wired to authenticate against Keycloak.
- Sign in to PuppyGraph through Keycloak and verify the user lands with the default RBAC role.
Stand-in identity provider
This tutorial uses a local Keycloak as a stand-in so you can try the flow end to end without standing up Okta, Auth0, Azure AD, or another production IdP. Production deployments point PuppyGraph at a real IdP. See the SSO configuration reference for the variables to use with your provider.
Prerequisites
Please ensure that docker compose is available. The installation can be verified by running:
Accessing the PuppyGraph Web UI requires a browser.
Setup
Deployment
Create a working directory and
cd into it:
Create
docker-compose.yaml with the following content:
docker-compose.yaml
services:
keycloak:
image: quay.io/keycloak/keycloak:26.0
command:
- start-dev
- --import-realm
- --http-port=18080
- --hostname=http://localhost:18080
- --health-enabled=true
environment:
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin
ports:
- "18080:18080"
volumes:
- ./keycloak-realm.json:/opt/keycloak/data/import/keycloak-realm.json:ro
healthcheck:
test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/18080"]
interval: 5s
timeout: 3s
retries: 30
start_period: 20s
puppygraph:
image: puppygraph/puppygraph:latest
ports:
- "8081:8081"
- "8182:8182"
- "7687:7687"
environment:
- PUPPYGRAPH_USERNAME=puppygraph
- PUPPYGRAPH_PASSWORD=puppygraph123
# SSO / OIDC
- SSO_ENABLED=true
- SSO_CLIENT_ID=puppygraph-app
- SSO_CLIENT_SECRET=puppygraph-sso-secret
- SSO_ISSUER=http://localhost:18080/realms/puppygraph
- SSO_URL=http://localhost:18080/realms/puppygraph/protocol/openid-connect/auth
- SSO_ACCESS_TOKEN_URL=http://keycloak:18080/realms/puppygraph/protocol/openid-connect/token
- SSO_JWKS_URL=http://keycloak:18080/realms/puppygraph/protocol/openid-connect/certs
- SSO_CALLBACK_URL=http://localhost:8081/sso_callback
- SSO_CLAIM_AS_USER_ID=preferred_username
# RBAC: assigns each SSO user a default role on first login
- RBAC_ENABLED=true
- RBAC_DEFAULT_ROLE=Analyst
depends_on:
keycloak:
condition: service_healthy
A note on the issuer: Keycloak in start-dev mode stamps the iss claim on issued tokens with whatever hostname it sees on the incoming request. The browser hits http://localhost:18080, but PuppyGraph (running in another container) reaches Keycloak through the compose network at http://keycloak:18080. Passing --hostname=http://localhost:18080 to Keycloak locks the issuer to localhost:18080 so both flows agree, and SSO_ISSUER validates correctly.
Keycloak Realm
Create
keycloak-realm.json alongside the compose file:
keycloak-realm.json
{
"realm": "puppygraph",
"enabled": true,
"sslRequired": "none",
"registrationAllowed": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": false,
"editUsernameAllowed": false,
"bruteForceProtected": false,
"accessTokenLifespan": 300,
"ssoSessionIdleTimeout": 1800,
"ssoSessionMaxLifespan": 36000,
"clients": [
{
"clientId": "puppygraph-app",
"name": "PuppyGraph SSO Client",
"enabled": true,
"clientAuthenticatorType": "client-secret",
"secret": "puppygraph-sso-secret",
"redirectUris": [
"http://localhost:8081/sso_callback",
"http://localhost:8081/*"
],
"webOrigins": ["http://localhost:8081"],
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"publicClient": false,
"protocol": "openid-connect",
"attributes": {
"pkce.code.challenge.method": "S256",
"post.logout.redirect.uris": "http://localhost:8081/*"
},
"defaultClientScopes": ["openid", "profile", "email"]
}
],
"users": [
{
"username": "testuser",
"enabled": true,
"email": "testuser@puppygraph.local",
"emailVerified": true,
"firstName": "Test",
"lastName": "User",
"credentials": [
{"type": "password", "value": "testpassword", "temporary": false}
]
}
]
}
The realm declares the OIDC client (puppygraph-app with secret puppygraph-sso-secret) and a single test user (testuser / testpassword). PKCE is enabled, the client secret authentication method is set to client-secret, and the redirect URI matches the SSO_CALLBACK_URL from the compose file.
Start the Stack
Wait about 60 to 90 seconds for both containers to become healthy. You can follow PuppyGraph's startup with:
Logging In
- Open http://localhost:8081. The login page now has a Sign in with SSO button alongside the local username / password form.
- Click it. You are redirected to Keycloak at
http://localhost:18080/.... Log in astestuser/testpassword. - You land back in PuppyGraph as
sso:testuserwith the defaultAnalystrole.
To verify the user record from the bootstrap admin, log out and sign in again as puppygraph / puppygraph123 (the local password form), then open Settings → Users. The sso:testuser row shows role: Analyst, role_source: default.
Cleanup
Shut down and remove the containers:
The -v flag also removes the Keycloak and PuppyGraph volumes so the next up -d starts with a fresh realm and a fresh user database.
What's Next
- Setting up Row-Level Security with Keycloak. Layer per-user row filtering on top of this SSO setup.
- SSO configuration reference. Swap Keycloak for a production identity provider (Okta, Auth0, Azure AD, Google Workspace).
- Role-Based Access Control. Assign roles to SSO users explicitly, or via IdP group mapping.