Configuring a Keycloak Realm for GeoNode SSO
This procedure walks through building a Keycloak realm and OIDC client that lets GeoNode delegate its entire login to Keycloak, so users authenticate once and land in the portal without a local password.
This is a hands-on companion to Keycloak vs Dex for OIDC Federation in Geospatial Portals and sits inside the wider Core Portal Architecture & Security Boundaries practice; read the parent guide if you still need to decide which provider fits. Here the provider is settled — Keycloak — and the goal is a working authorization-code login where GeoNode is a relying party and Keycloak issues the tokens.
Prerequisites
Confirm each of the following before you touch the admin console. A skipped item here is the usual cause of a login that half-works.
- Keycloak 24+ reachable over HTTPS at a stable hostname (examples use
https://id.agency.gov). Behind a proxy, setKC_PROXY_HEADERS=xforwardedandKC_HOSTNAMEso issued URLs match the public origin. - A running GeoNode 4.x you can restart, with shell or environment access to its Django settings.
- Realm-admin rights in Keycloak (the built-in
adminaccount or a delegated realm admin). - The portal’s public base URL, e.g.
https://portal.agency.gov, and its OIDC callback path —/#/oidc/callback/or/o/callback/depending on your GeoNode auth integration. - Decide the integration path up front: GeoNode’s Django social login (via
mozilla-django-oidcor adjango-allauthOpenID Connect provider), ormod_auth_openidcat the Apache/NGINX proxy. This guide shows the Django path and notes the proxy alternative. curland a JWT decoder (jwt-cli, orjqplusbase64 -d) for verification.
The sequence below is the authorization-code flow you are about to configure: the browser bounces from GeoNode to Keycloak and back with a code that GeoNode exchanges for tokens over a back channel.
Step-by-step implementation
1. Create the realm
A dedicated realm isolates the portal’s identity from Keycloak’s master administrative realm, which should never host application users. Create geoportal from the console, or import it declaratively so the realm is reproducible.
{
"realm": "geoportal",
"enabled": true,
"sslRequired": "external",
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"registrationAllowed": false,
"accessTokenLifespan": 300,
"ssoSessionIdleTimeout": 1800,
"ssoSessionMaxLifespan": 36000
}
Import it with kcadm.sh create realms -f geoportal-realm.json or via Realm settings → Partial import. Keeping the realm as a checked-in JSON file means staging and production start from the same definition rather than hand-clicked drift.
2. Create a confidential OIDC client
GeoNode runs server-side and can keep a secret, so register a confidential client using the standard authorization-code flow. Public clients (no secret) are for browser-only apps and are the wrong choice here.
{
"clientId": "geonode-portal",
"protocol": "openid-connect",
"publicClient": false,
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": false,
"attributes": {
"post.logout.redirect.uris": "https://portal.agency.gov/*",
"pkce.code.challenge.method": "S256"
}
}
After creating the client, open its Credentials tab and copy the generated secret — GeoNode needs it in the next steps. Enabling PKCE with S256 hardens the code exchange even for a confidential client.
3. Set valid redirect URIs and web origins
Keycloak refuses to return a code to any callback that is not on the client’s allow-list, so these values must match GeoNode’s callback exactly. An over-broad wildcard is a security risk; an incorrect path produces an Invalid redirect_uri error at login.
{
"clientId": "geonode-portal",
"rootUrl": "https://portal.agency.gov",
"baseUrl": "https://portal.agency.gov/",
"redirectUris": [
"https://portal.agency.gov/oidc/callback/",
"https://portal.agency.gov/o/callback/"
],
"webOrigins": [
"https://portal.agency.gov"
]
}
Set webOrigins to the portal’s exact origin (not *) so the CORS headers Keycloak emits on its endpoints permit the browser calls GeoNode’s frontend makes during login.
4. Add protocol mappers
Mappers decide which claims land in the token. For SSO you need the user’s email and preferred username reliably present; add a groups mapper too if you plan to drive roles from group membership, which the companion guide covers in depth.
{
"protocolMappers": [
{
"name": "username",
"protocolMapper": "oidc-usermodel-property-mapper",
"protocol": "openid-connect",
"config": {
"user.attribute": "username",
"claim.name": "preferred_username",
"id.token.claim": "true",
"access.token.claim": "true"
}
},
{
"name": "groups",
"protocolMapper": "oidc-group-membership-mapper",
"protocol": "openid-connect",
"config": {
"claim.name": "groups",
"full.path": "false",
"id.token.claim": "true",
"access.token.claim": "true"
}
}
]
}
Setting full.path to false emits bare group names (gis-editors) rather than hierarchical paths (/agency/gis-editors), which keeps the downstream mapping simpler. How GeoNode consumes that claim is detailed in Mapping OIDC Group Claims to GeoNode Roles.
5. Wire GeoNode social login
With the client in place, point GeoNode at the realm. Using mozilla-django-oidc, the Django settings reference Keycloak’s discovery-derived endpoints and the confidential client’s credentials.
# geonode local_settings.py — OIDC relying-party configuration
OIDC_RP_CLIENT_ID = "geonode-portal"
OIDC_RP_CLIENT_SECRET = os.environ["OIDC_RP_CLIENT_SECRET"]
OIDC_RP_SIGN_ALGO = "RS256"
_ISSUER = "https://id.agency.gov/realms/geoportal"
OIDC_OP_AUTHORIZATION_ENDPOINT = f"{_ISSUER}/protocol/openid-connect/auth"
OIDC_OP_TOKEN_ENDPOINT = f"{_ISSUER}/protocol/openid-connect/token"
OIDC_OP_USER_ENDPOINT = f"{_ISSUER}/protocol/openid-connect/userinfo"
OIDC_OP_JWKS_ENDPOINT = f"{_ISSUER}/protocol/openid-connect/certs"
OIDC_RP_SCOPES = "openid email profile groups"
LOGIN_REDIRECT_URL = "/"
AUTHENTICATION_BACKENDS = (
"mozilla_django_oidc.auth.OIDCAuthenticationBackend",
"django.contrib.auth.backends.ModelBackend",
)
Keeping the client secret in an environment variable rather than the settings file lets you rotate it without editing code. If you prefer to authenticate at the proxy instead of in Django, mod_auth_openidc consumes the same realm — set OIDCProviderMetadataURL to https://id.agency.gov/realms/geoportal/.well-known/openid-configuration, OIDCClientID geonode-portal, and OIDCRedirectURI to the callback — and passes the authenticated user to GeoNode through a request header.
6. Enable and expose SSO login
Restart GeoNode so the OIDC backend loads, then add the login route to the URL config and surface a “Sign in with agency SSO” link so users reach the flow.
# urls.py — expose the OIDC login/callback routes
from django.urls import path, include
urlpatterns = [
path("oidc/", include("mozilla_django_oidc.urls")),
# ... existing GeoNode routes ...
]
Restart with your process manager (systemctl restart geonode or a rolling pod restart) and the “Sign in” button now redirects to Keycloak. For the multi-tenant permission model these authenticated sessions plug into, align the realm’s groups with Implementing RBAC for Multi-Tenant GIS Portals.
Verification
Confirm the provider metadata, the token exchange, and an end-to-end login before declaring SSO live.
# 1. The realm's discovery document resolves and lists the expected endpoints
curl -fsS https://id.agency.gov/realms/geoportal/.well-known/openid-configuration | jq '.issuer, .token_endpoint, .jwks_uri'
# "https://id.agency.gov/realms/geoportal"
# "https://id.agency.gov/realms/geoportal/protocol/openid-connect/token"
# "https://id.agency.gov/realms/geoportal/protocol/openid-connect/certs"
# 2. The confidential client can obtain a token (use a test user; direct grant enabled only for this check)
curl -fsS -X POST https://id.agency.gov/realms/geoportal/protocol/openid-connect/token \
-d grant_type=password -d client_id=geonode-portal \
-d client_secret="$OIDC_RP_CLIENT_SECRET" \
-d username=testuser -d password="$TEST_PW" -d scope="openid email" | jq '.access_token != null'
# true
# 3. The JWKS endpoint serves the signing keys the token was signed with
curl -fsS https://id.agency.gov/realms/geoportal/protocol/openid-connect/certs | jq '.keys[0].kid'
# 4. Browser check: visiting the portal redirects to Keycloak and returns authenticated
curl -fsS -I https://portal.agency.gov/oidc/authenticate/ | grep -i location
# location: https://id.agency.gov/realms/geoportal/protocol/openid-connect/auth?...
A non-null access_token in step 2 and a location header pointing at the realm’s authorize endpoint in step 4 confirm the client, secret, and redirect wiring are correct end to end. Disable the temporary direct-access grant after this check.
Troubleshooting matrix
| Symptom | Likely cause | Fix |
|---|---|---|
Invalid redirect_uri on the Keycloak login page |
Callback URL not in the client’s redirectUris |
Add the exact GeoNode callback (trailing slash included) to the client |
invalid_client at the token endpoint |
Wrong or rotated client secret in GeoNode | Copy a fresh secret from the client’s Credentials tab into OIDC_RP_CLIENT_SECRET |
| Login succeeds at Keycloak but GeoNode shows anonymous | OIDC backend not in AUTHENTICATION_BACKENDS, or URLs not included |
Add the backend and include("mozilla_django_oidc.urls"); restart GeoNode |
| Issuer URL in tokens uses an internal hostname | KC_HOSTNAME/proxy headers not set behind the reverse proxy |
Set KC_HOSTNAME and KC_PROXY_HEADERS=xforwarded so issued URLs match the public origin |
Signature verification failed in Django logs |
JWKS endpoint wrong or algorithm mismatch | Point OIDC_OP_JWKS_ENDPOINT at /certs; set OIDC_RP_SIGN_ALGO=RS256 |
| CORS error during the frontend callback | Portal origin missing from client webOrigins |
Add the exact portal origin to webOrigins (never use *) |
| Users authenticate but have no groups | Groups mapper not added to the client scope | Add the group-membership mapper from step 4 and re-test the token |
Related
- Mapping OIDC Group Claims to GeoNode Roles — consume the
groupsclaim this realm emits and turn it into GeoNode roles. - Implementing RBAC for Multi-Tenant GIS Portals — the authorization model these authenticated sessions feed.
Up one level: Keycloak vs Dex for OIDC Federation in Geospatial Portals.