Keycloak Setup for Octopus
This document describes what the IAM/infrastructure team needs to configure in Keycloak
to enable keycloak in AUTH_PROVIDER for an Octopus deployment.
This setup assumes Octopus is being registered as a new client inside an existing realm that already hosts other applications. Octopus uses the Authorization Code + PKCE flow — no client secret is required.
Placeholders used throughout this document
Placeholder
Replace with
https://keycloak.example.orgYour Keycloak server URL
myrealmYour Keycloak realm name
octopusThe client ID you choose for Octopus
1. Register the Octopus client
In the Keycloak Admin Console (https://keycloak.example.org/admin):
Select the target realm (
myrealm).Go to Clients → Create client.
Fill in the General settings tab:
Setting
Value
Client type
OpenID ConnectClient ID
octopusName
OctopusDescription
Octopus monitoring dashboard
Capability config tab — see Public client explained below:
Setting
Value
Client authentication
Off
Standard flow
On
Direct access grants
Off
All others
Off
Login settings tab — see Multiple deployments below:
Setting
Value
Root URL
(leave blank)
Home URL
(leave blank)
Valid redirect URIs
one entry per deployment (see below)
Valid post logout redirect URIs
one entry per deployment (see below)
Web origins
+(special value — auto-derives from redirect URIs)Save the client.
Public client explained
What “Client authentication: Off” means
Keycloak supports two client types:
Type |
Client authentication |
How it proves its identity |
|---|---|---|
Public (what we use) |
Off |
Does not prove its identity at all |
Confidential |
On |
Authenticates with a client secret or certificate |
A public client cannot hold a secret safely — the Octopus frontend runs entirely
in the user’s browser, so any secret embedded in the JavaScript bundle or config.js
would be visible to anyone who opens DevTools. There is no server-side component that
could keep a secret on the frontend’s behalf.
Is this insecure? No, as long as PKCE is used (which Octopus does).
PKCE (Proof Key for Code Exchange) replaces the client secret for public clients:
Before redirecting to Keycloak, the browser generates a random
code_verifier.It sends a hashed version (
code_challenge) to Keycloak with the login request.After the user authenticates, Keycloak returns a one-time
code.The browser exchanges that
codefor a token by also sending the originalcode_verifier. Keycloak checks the hash — only the browser that started the flow has the unhashedcode_verifier, so a third party who intercepts thecodecannot use it.
This is the same mechanism used by mobile apps and desktop applications. Keycloak enforces PKCE automatically when the client is public.
Turn on Standard flow, everything else off. “Standard flow” is the Authorization Code flow — the only flow compatible with PKCE. “Direct access grants” (Resource Owner Password) bypasses PKCE and the Keycloak login page; it should be off for browser clients.
Multiple deployments
Why Root URL and Home URL are usually left blank
These two fields serve as a base prefix that Keycloak prepends to any relative
paths you enter in “Valid redirect URIs”. If you set Root URL = https://octopus.example.org
and then enter /callback as a redirect URI, Keycloak expands it to
https://octopus.example.org/callback.
When you have multiple deployments at different origins, leave both fields blank. Enter each deployment as its own full absolute URI instead — Keycloak won’t prepend anything, and all of them are accepted.
Configuring all Octopus deployment URLs
Add one wildcard entry per deployment origin to Valid redirect URIs:
http://localhost:3000/*
http://192.168.0.2/*
http://192.168.0.3/*
http://192.168.49.2/integration-octopus/ska-octopus-frontend/*
https://k8s.stfc.skao.int/integration-octopus/ska-octopus-frontend/*
https://k8s.mid.internal.skao.int/octopus/ska-octopus-frontend/*
https://k8s.low.internal.skao.int/octopus/ska-octopus-frontend/*
https://k8s.miditf.internal.skao.int/octopus/ska-octopus-frontend/*
https://k8s.lowitf.internal.skao.int/octopus/ska-octopus-frontend/*
The trailing /* lets Keycloak accept any path under that origin, which covers
both the root and deployments under a sub-path.
Add the same list (without /*) to Valid post logout redirect URIs — these are
the pages Keycloak redirects to after a user logs out:
http://localhost:3000
http://192.168.0.2
http://192.168.0.3
http://192.168.49.2/integration-octopus/ska-octopus-frontend
https://k8s.stfc.skao.int/integration-octopus/ska-octopus-frontend
https://k8s.mid.internal.skao.int/octopus/ska-octopus-frontend
https://k8s.low.internal.skao.int/octopus/ska-octopus-frontend
https://k8s.miditf.internal.skao.int/octopus/ska-octopus-frontend
https://k8s.lowitf.internal.skao.int/octopus/ska-octopus-frontend
For Web origins, enter the special value +. This tells Keycloak to
derive the allowed CORS origins automatically from the redirect URI list above —
you do not need to maintain a separate list. Every origin you added as a redirect
URI will be permitted to make cross-origin requests to Keycloak’s token endpoint.
One client for all deployments: all deployments share the same
octopusclient. You do not need a separate client per environment — Keycloak matches the specific URL at login time and returns to exactly that URL. Each deployment has its ownconfig.jspointing to the sameKEYCLOAK_CLIENT_ID.
Per-deployment config.js
Each deployment’s public/config.js is identical except for AUTH_PROVIDER if
some environments use only local auth. The Keycloak settings are always the same:
// All deployments use the same Keycloak settings
KEYCLOAK_URL: 'https://keycloak.example.org',
KEYCLOAK_REALM: 'myrealm',
KEYCLOAK_CLIENT_ID: 'octopus',
The redirect URI Keycloak uses is determined at runtime from the browser’s
window.location — keycloak-js sends the current page URL automatically.
You do not need to configure it anywhere in config.js.
2. Create client roles
Octopus maps Keycloak roles to its own internal role levels (Viewer → Admin).
The recommended approach is to use client roles inside the octopus client —
this keeps Octopus roles isolated from other applications in the realm.
Role naming and ROLE_PREFIX
Before creating the roles, decide whether this deployment uses a role prefix. A prefix scopes role names to a specific telescope or site so that a user in a shared Keycloak realm can hold assignments for multiple deployments without them conflicting.
|
Role names to create |
|---|---|
(empty) |
|
|
|
|
|
|
|
Set ROLE_PREFIX to the same value in both the backend environment and the frontend
config.js. Leave it empty to keep the legacy unprefixed names.
Selection rules when a user holds roles from multiple deployments:
Only roles matching the deployment’s own prefix are considered; others are silently ignored.
If a user holds several roles under the same prefix (e.g.
octopus-low-operatorandoctopus-low-admin), the highest level wins — no need to remove lower roles.
Creating the roles
Go to Clients → octopus → Roles → Create role and create these five roles
(example uses ROLE_PREFIX=low — substitute your prefix or omit it):
Role name |
Octopus role |
Access level |
|---|---|---|
|
Viewer (level 1) |
Read-only |
|
User (level 2) |
Standard operations |
|
Operator (level 3) |
Telescope operator access |
|
Engineer (level 4) |
Engineering / configuration |
|
Admin (level 5) |
Full administrative access |
Role hierarchy: levels are inclusive — an Operator can do everything a User and Viewer can. Users only need to be assigned to their highest role.
Alternative: realm roles
If you prefer realm-level roles (shared across applications), create the same role
names under Realm roles instead. The backend resolves roles from both
realm_access.roles and resource_access.<client_id>.roles, so either approach works.
Set KEYCLOAK_ROLE_* environment variables to match whatever names you choose if they
differ from the ROLE_PREFIX-derived defaults.
3. Add an audience mapper (recommended for production)
Some Keycloak deployments issue access tokens with "aud": "account" and only
identify the Octopus client via "azp": "octopus". The backend skips audience
validation unless KEYCLOAK_AUDIENCE is set.
If you want to make the audience claim explicit (good practice in a multi-app realm):
Clients →
octopus→ Client scopes →octopus-dedicated.Add mapper → By configuration → Audience.
Set Included client audience to
octopusand Add to access token: On.
This is optional but makes the token contract explicit and is easy to add later.
4. Assign users
For each user who needs Octopus access:
Go to Users →
<username>→ Role mapping.Click Assign role → Filter by clients →
octopus.Assign exactly one Octopus client role (their highest level).
Users with no Octopus role can still authenticate via Keycloak and receive
Vieweraccess by default. SetKEYCLOAK_ROLE_VIEWERto an empty string in the backend environment to reject users without an explicit role assignment.
5. Backend environment variables
# Enable Keycloak (alongside or instead of local auth)
AUTH_PROVIDER=local,keycloak
# Keycloak connection
KEYCLOAK_URL=https://keycloak.example.org
KEYCLOAK_REALM=myrealm
KEYCLOAK_CLIENT_ID=octopus
# Audience claim validation (set only after adding the audience mapper above)
KEYCLOAK_AUDIENCE=octopus
# Deployment-specific role prefix — must match the role names created in step 2
# and the ROLE_PREFIX set in the frontend config.js.
# When set, role names default to octopus-<prefix>-viewer, octopus-<prefix>-admin, …
# Leave empty for legacy unprefixed names (octopus-viewer, octopus-admin, …).
ROLE_PREFIX=low
# Map Keycloak role names to Octopus role levels.
# Only needed if you used non-standard names that don't follow the ROLE_PREFIX pattern.
# KEYCLOAK_ROLE_ADMIN=octopus-low-admin
# KEYCLOAK_ROLE_ENGINEER=octopus-low-engineer
# KEYCLOAK_ROLE_OPERATOR=octopus-low-operator
# KEYCLOAK_ROLE_USER=octopus-low-user
# KEYCLOAK_ROLE_VIEWER=octopus-low-viewer
The backend fetches JWKS automatically from:
https://keycloak.example.org/realms/myrealm/protocol/openid-connect/certs
JWKS keys are cached in memory and refreshed automatically on key rotation.
6. Frontend configuration (public/config.js)
AUTH_PROVIDER: ['local', 'keycloak'], // or just ['keycloak'] to disable local login
KEYCLOAK_URL: 'https://keycloak.example.org',
KEYCLOAK_REALM: 'myrealm',
KEYCLOAK_CLIENT_ID: 'octopus',
// Must match ROLE_PREFIX in the backend environment.
// The frontend uses this to derive the expected Keycloak role names from JWT claims
// before the backend role sync completes. Leave empty for legacy unprefixed names.
ROLE_PREFIX: 'low',
// No KEYCLOAK_REDIRECT_URI needed — keycloak-js uses the current page URL automatically.
If the role names in your Keycloak realm differ from the ROLE_PREFIX-derived pattern,
you can pin them individually:
// Only needed when role names don't follow the octopus-<prefix>-<role> pattern:
KEYCLOAK_ROLE_ADMIN: 'my-custom-admin-role',
KEYCLOAK_ROLE_ENGINEER: 'my-custom-engineer-role',
KEYCLOAK_ROLE_OPERATOR: 'my-custom-operator-role',
KEYCLOAK_ROLE_USER: 'my-custom-user-role',
KEYCLOAK_ROLE_VIEWER: 'my-custom-viewer-role',
For a production deployment that disables the local username/password fallback, set
AUTH_PROVIDER: ['keycloak']in both the frontend config and the backendAUTH_PROVIDERenvironment variable.
7. How the authentication flow works
User clicks "Login (Keycloak)"
└─→ Frontend redirects to Keycloak login page (PKCE, code_challenge)
└─→ User authenticates in Keycloak
└─→ Keycloak redirects back to the current deployment URL with ?code=...
└─→ keycloak-js exchanges code for access token (RS256 JWT)
└─→ Token stored in localStorage (octopus.jwt)
└─→ Every API request includes Authorization: Bearer <token>
└─→ Backend fetches JWKS, verifies RS256 signature
└─→ Maps client roles → Octopus role level
└─→ GraphQL resolvers enforce minimum level
Token refresh is handled automatically by keycloak-js — the user is silently
re-authenticated before the token expires without needing to log in again.
8. Verification checklist
After completing the setup, verify the integration:
Open Octopus — a Keycloak login button is visible alongside (or instead of) Local.
Click Keycloak — browser redirects to the Keycloak realm login page.
Authenticate as a test user — redirect returns to Octopus.
User is logged in and their name appears in the header.
Check the Profile tab in Settings — it shows the Keycloak issuer URL.
GET /auth/mereturns the correctroleandrole_level.Log out — browser redirects to the Keycloak logout endpoint and the session is cleared.
Repeat the login test from a second deployment URL (e.g. localhost and one of the k8s URLs).
If authentication fails, check:
Redirect URI mismatch — the most common error. Keycloak rejects the login if the URL it redirected to is not in the Valid redirect URIs list. The error message will say
Invalid parameter: redirect_uri. Add the missing URL to the list.CORS error in the browser console — the origin is not in Web origins. Ensure you used
+(not a manual list) so all redirect URI origins are included.Backend logs — look for
Failed to fetch Keycloak JWKS(network/firewall issue) orCould not validate credentials(wrong audience or expired token).Audience mismatch — if
KEYCLOAK_AUDIENCEis set but the tokenaudclaim differs, validation fails silently. Inspect the token with:GET /auth/validate?token=<paste-token-here>
or paste it into jwt.io to read the
audclaim.
9. Running multiple auth providers simultaneously
Octopus supports running local, keycloak, and msal at the same time.
Each provider shows its own login button; the backend detects the provider from
the JWT iss claim at validation time.
# Backend: all three providers enabled
AUTH_PROVIDER=local,keycloak,msal
// Frontend config.js
AUTH_PROVIDER: ['local', 'keycloak', 'msal'],
Users from any provider can log in concurrently. Each session is completely independent.