Skip to content

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:

docker compose version

Accessing the PuppyGraph Web UI requires a browser.

Setup

Deployment

▶ Create a working directory and cd into it:

mkdir puppygraph-sso-demo
cd puppygraph-sso-demo

▶ 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

docker compose up -d

Wait about 60 to 90 seconds for both containers to become healthy. You can follow PuppyGraph's startup with:

docker compose logs -f puppygraph

Logging In

  1. Open http://localhost:8081. The login page now has a Sign in with SSO button alongside the local username / password form.
  2. Click it. You are redirected to Keycloak at http://localhost:18080/.... Log in as testuser / testpassword.
  3. You land back in PuppyGraph as sso:testuser with the default Analyst role.

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:

docker compose down -v

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