Keycloak + Grafana: Set Up SSO for Your Monitoring Dashboard
If you're running Grafana for monitoring and Keycloak for identity, there's a good chance your team is still logging into both separately. That's fine for two people. It's a nightmare for twenty.
Here's how to wire them together so your team logs into Grafana through Keycloak — with automatic role mapping, so admins get admin access and viewers stay viewers. No manual user management in Grafana, no shared passwords floating around in Slack. The whole thing takes about 15 minutes.
What You'll Need
- A running Keycloak instance (v26.x) with admin access
- A running Grafana instance (v12.x) with config file or environment variable access
- Both accessible over HTTPS (handled automatically on Elestio)
If you don't have these yet, deploy them on Elestio in under 5 minutes:
Step 1: Create an OIDC Client in Keycloak
Open your Keycloak Admin Console and navigate to your realm (e.g., myorg).
- Go to Clients → Create client
- Set Client type to
OpenID Connect - Set Client ID to
grafana - Turn Client authentication to ON (this makes it a confidential client)
- Enable Standard flow
- Click Save
Now configure the URLs:
- Valid redirect URIs:
https://grafana.example.com/login/generic_oauth - Valid post logout redirect URIs:
https://grafana.example.com/login - Web origins:
https://grafana.example.com
Go to the Credentials tab and copy the Client Secret. You'll need it for Grafana's config.
Step 2: Create Roles and a Role Mapper
Still in Keycloak, go to Clients → grafana → Roles tab and create these client roles:
grafanaadmin— maps to Grafana Server Adminadmin— maps to Grafana Org Admineditor— maps to Grafana Editor
Users without any of these roles default to Viewer.
Now the critical part — you need a mapper to include roles in the token. Without this, every user silently falls back to Viewer regardless of their assigned roles.
- Go to Clients → grafana → Client scopes → grafana-dedicated
- Click Add mapper → By configuration → User Client Role
- Configure:
- Name:
roles - Client ID:
grafana - Token Claim Name:
roles - Multivalued: ON
- Add to ID token: ON
- Add to access token: ON
- Name:
This is the step most tutorials skip, and it's the #1 reason SSO role mapping "doesn't work."
Step 3: Configure Grafana
On Elestio, access your Grafana container and add these environment variables to your docker-compose.yml:
environment:
GF_SERVER_ROOT_URL: "https://grafana.example.com"
GF_AUTH_GENERIC_OAUTH_ENABLED: "true"
GF_AUTH_GENERIC_OAUTH_NAME: "Keycloak"
GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP: "true"
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "grafana"
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "your-client-secret-here"
GF_AUTH_GENERIC_OAUTH_SCOPES: "openid email profile offline_access roles"
GF_AUTH_GENERIC_OAUTH_EMAIL_ATTRIBUTE_PATH: "email"
GF_AUTH_GENERIC_OAUTH_LOGIN_ATTRIBUTE_PATH: "username"
GF_AUTH_GENERIC_OAUTH_NAME_ATTRIBUTE_PATH: "full_name"
GF_AUTH_GENERIC_OAUTH_AUTH_URL: "https://keycloak.example.com/realms/myorg/protocol/openid-connect/auth"
GF_AUTH_GENERIC_OAUTH_TOKEN_URL: "https://keycloak.example.com/realms/myorg/protocol/openid-connect/token"
GF_AUTH_GENERIC_OAUTH_API_URL: "https://keycloak.example.com/realms/myorg/protocol/openid-connect/userinfo"
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: "contains(roles[*], 'grafanaadmin') && 'GrafanaAdmin' || contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer'"
GF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_GRAFANA_ADMIN: "true"
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_STRICT: "true"
GF_AUTH_GENERIC_OAUTH_USE_REFRESH_TOKEN: "true"
Replace grafana.example.com, keycloak.example.com, myorg, and your-client-secret-here with your actual values.
Restart Grafana:
cd /opt/app
docker-compose down && docker-compose up -d
Step 4: Test the Login
Open Grafana in your browser. You should now see a "Sign in with Keycloak" button on the login page. Click it, authenticate with a Keycloak user, and you'll land in Grafana with the role matching your Keycloak assignment.
To verify role mapping is working, assign the admin client role to a test user in Keycloak, log in through Grafana, and check that the user has Org Admin permissions.
How the Role Mapping Works
The role_attribute_path uses JMESPath to evaluate roles from the token:
contains(roles[*], 'grafanaadmin') && 'GrafanaAdmin'
|| contains(roles[*], 'admin') && 'Admin'
|| contains(roles[*], 'editor') && 'Editor'
|| 'Viewer'
It checks in order — first match wins. A user with both grafanaadmin and editor gets GrafanaAdmin. You can test JMESPath expressions at play.jmespath.org using a sample JWT payload.
Troubleshooting
"Invalid parameter: redirect_uri" — The most common error. Make sure Keycloak's "Valid redirect URIs" exactly matches https://your-grafana-url/login/generic_oauth. No trailing slash. And make sure GF_SERVER_ROOT_URL is set to your external URL, not localhost:3000.
All users get Viewer role — You forgot the role mapper in Step 2. Go back to Keycloak, add the User Client Role mapper, and make sure "Add to ID token" is ON.
Login fails with "missing required scope" — The user needs the offline_access realm role in Keycloak when offline_access is in the scopes list. Assign it under Users → Role mappings → Realm roles.
Old Keycloak URL format — If you're following old tutorials, note that Keycloak 17+ dropped /auth from URLs. Use https://keycloak.example.com/realms/myorg/... not https://keycloak.example.com/auth/realms/myorg/....
Logout doesn't redirect properly — Add the signout redirect URL as an environment variable: GF_AUTH_GENERIC_OAUTH_SIGNOUT_REDIRECT_URL pointing to Keycloak's logout endpoint with post_logout_redirect_uri back to Grafana's login page.
Thanks for reading. See you in the next one.