Grafana SSO with Keycloak (and Securing Prometheus). Set up Grafana single sign-on with Keycloak via OIDC, map Keycloak roles to Grafana org roles, and secure Prometheus behind oauth2-proxy and Keycloak.
Last updated: September 2026
Grafana has built-in generic OAuth/OIDC support, so you point it at your Keycloak realm and map a Keycloak role claim to Grafana’s Admin, Editor, or Viewer org roles via role_attribute_path. Prometheus has no native authentication whatsoever, to protect it, you place oauth2-proxy in front of it, configured with Keycloak as the OIDC provider, so every request to the Prometheus UI passes through a Keycloak login before reaching the metrics endpoint.
This tutorial walks through both integrations end to end: registering a Grafana client in Keycloak, adding a roles mapper so the access token carries the right claims, configuring Grafana’s [auth.generic_oauth] section, and then deploying oauth2-proxy in front of Prometheus. By the end, your entire observability stack will authenticate through Keycloak, and your team can use a single set of credentials across all internal tooling.
If you are not yet familiar with OIDC token flows, our OpenID Connect primer for developers covers the concepts you will encounter in every step below.
Why Route Grafana and Prometheus Through Keycloak
Running a separate username and password per tool creates three concrete problems. First, offboarding is error-prone, remove the person from Keycloak and they still have access to Grafana if you forgot to delete their local account. Second, MFA is inconsistent, you can enforce multi-factor authentication at the Keycloak level and every OIDC-integrated tool inherits it automatically. Third, audit logging is fragmented, Keycloak records every login event in a single place, which simplifies compliance reporting.
For a broader look at how SSO removes per-application credential sprawl, see the SSO implementation guide for developers.
Prerequisites
- Keycloak 26.x running and reachable (e.g.,
https://auth.example.com) - A realm already created, this guide uses a realm named
monitoring - Grafana 10.x or later
- Prometheus and oauth2-proxy (for the Prometheus section)
- Basic familiarity with Keycloak client configuration, if roles and scopes are new, start with Keycloak client scopes vs roles explained
Step 1: Register Grafana as a Keycloak Client
Open the Keycloak Admin Console, navigate to your realm (monitoring), and go to Clients > Create client.
Fill in the following:
| Field | Value |
|---|---|
| Client ID | grafana |
| Client type | OpenID Connect |
| Client authentication | On (confidential) |
| Standard flow | Enabled |
| Direct access grants | Disabled |
On the next screen, set the Valid redirect URIs:
https://grafana.example.com/login/generic_oauth
Set the Valid post logout redirect URIs to your Grafana base URL:
https://grafana.example.com
Save the client. Go to the Credentials tab and copy the client secret, you will need it in the Grafana configuration.
Step 2: Create Keycloak Roles for Grafana
Grafana maps Keycloak roles to its three org roles: Admin, Editor, and Viewer. You need to create matching realm roles in Keycloak.
In the Admin Console, go to Realm roles > Create role and create three roles:
grafana-admingrafana-editorgrafana-viewer
Assign these roles to your test users now (go to the user, Role mapping > Assign role), so you can verify the mapping works at the end of the tutorial.
Step 3: Add a Roles Mapper to the Grafana Client
By default, Keycloak does not include realm roles in the access token that Grafana reads. You need to add a protocol mapper.
Go to Clients > grafana > Client scopes, click the dedicated scope (usually named grafana-dedicated), then go to Mappers > Add mapper > By configuration.
Choose User Realm Role and configure it:
| Field | Value |
|---|---|
| Name | grafana-roles |
| Token Claim Name | roles |
| Claim JSON Type | String |
| Add to ID token | On |
| Add to access token | On |
| Add to userinfo | On |
| Multivalued | On |
This mapper injects a roles array into every token issued to the grafana client. If the user has the grafana-admin realm role, the token will contain "roles": ["grafana-admin"].
You can verify the token contents using the JWT Token Analyzer, paste the raw token and confirm the roles claim is present before moving on.
Step 4: Configure Grafana Generic OAuth
Grafana reads its OAuth configuration from grafana.ini (or environment variables prefixed with GF_). The relevant section is [auth.generic_oauth].
Grafana’s generic OAuth documentation covers every available key, the configuration below focuses on the settings you must set for a Keycloak integration.
grafana.ini approach
[auth.generic_oauth]
enabled = true
name = Keycloak
allow_sign_up = true
client_id = grafana
client_secret = <paste-secret-from-credentials-tab>
scopes = openid profile email
auth_url = https://auth.example.com/realms/monitoring/protocol/openid-connect/auth
token_url = https://auth.example.com/realms/monitoring/protocol/openid-connect/token
api_url = https://auth.example.com/realms/monitoring/protocol/openid-connect/userinfo
role_attribute_path = contains(roles[*], 'grafana-admin') && 'Admin' || contains(roles[*], 'grafana-editor') && 'Editor' || 'Viewer'
Environment variable approach (Docker/Kubernetes)
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=<paste-secret>
GF_AUTH_GENERIC_OAUTH_SCOPES=openid profile email
GF_AUTH_GENERIC_OAUTH_AUTH_URL=https://auth.example.com/realms/monitoring/protocol/openid-connect/auth
GF_AUTH_GENERIC_OAUTH_TOKEN_URL=https://auth.example.com/realms/monitoring/protocol/openid-connect/token
GF_AUTH_GENERIC_OAUTH_API_URL=https://auth.example.com/realms/monitoring/protocol/openid-connect/userinfo
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH=contains(roles[*], 'grafana-admin') && 'Admin' || contains(roles[*], 'grafana-editor') && 'Editor' || 'Viewer'
Understanding role_attribute_path
The role_attribute_path value is a JMESPath expression evaluated against the token claims. Grafana evaluates it left to right, assigning the first matching role. The three valid return values are Admin, Editor, and Viewer (exact case).
The expression above:
- Checks whether the
rolesarray containsgrafana-admin, if yes, assignsAdmin - Checks whether the
rolesarray containsgrafana-editor, if yes, assignsEditor - Falls through to
Viewerfor everyone else
If a user has no Keycloak roles at all, they land as Viewer. If you prefer to block unauthenticated Grafana access entirely (no auto-assignment), set role_attribute_strict = true, Grafana will deny login if the expression returns an empty result.
Role Mapping Reference
| Keycloak Realm Role | Grafana Org Role | Permissions |
|---|---|---|
grafana-admin |
Admin | Manage org, datasources, users, dashboards |
grafana-editor |
Editor | Create and edit dashboards |
grafana-viewer |
Viewer | Read-only access to dashboards |
| (no role) | Viewer (default) or denied | Depends on role_attribute_strict |
Step 5: Enable Auto-Login and Logout
To skip the Grafana login page entirely and send users straight to Keycloak, add these lines:
[auth]
oauth_auto_login = true
[auth.generic_oauth]
; ... existing config above ...
signout_redirect_url = https://auth.example.com/realms/monitoring/protocol/openid-connect/logout?post_logout_redirect_uri=https://grafana.example.com&client_id=grafana
oauth_auto_login = true redirects unauthenticated users to Keycloak immediately instead of showing the Grafana login screen. The signout_redirect_url ensures that when a user clicks Sign Out in Grafana, they are also logged out of the Keycloak session, otherwise they would be immediately logged back in on the next page load.
Somewhere to run this that stays patched
Everything above works the same on Skycloak, because it is real upstream Keycloak rather than a fork. What changes is who handles the upgrades, backups and security patches afterwards.
Step 6: Verify the Integration
Restart Grafana and open https://grafana.example.com in a private browser window. You should be redirected to the Keycloak login page for the monitoring realm. Sign in as a user with the grafana-admin role. After authentication, Keycloak redirects back to Grafana with a valid code, Grafana exchanges it for tokens, evaluates role_attribute_path, and logs you in as an Admin.
Check Server Admin > Users in Grafana to confirm the user was created with the correct org role.
Securing Prometheus: Why It Needs a Proxy
Prometheus has no built-in authentication. The official Prometheus documentation states this explicitly: the web UI and HTTP API accept any request from any client that can reach the network socket. If you expose Prometheus directly, even on an internal network, anyone with network access can read all your metrics, which often includes infrastructure topology, service names, and load patterns that should not be public.
The standard solution is to place an authenticating reverse proxy in front of Prometheus. oauth2-proxy is the most widely used option, it validates OIDC sessions and forwards authenticated requests to an upstream, in this case Prometheus.
For background on running Keycloak behind a proxy layer, the Keycloak reverse proxy guide explains the forwarding header requirements that apply here too.
Step 7: Register oauth2-proxy as a Keycloak Client
Back in the Keycloak Admin Console, create a second confidential client for oauth2-proxy:
| Field | Value |
|---|---|
| Client ID | prometheus-proxy |
| Client type | OpenID Connect |
| Client authentication | On (confidential) |
| Standard flow | Enabled |
| Direct access grants | Disabled |
Set the Valid redirect URIs:
https://prometheus.example.com/oauth2/callback
Copy the client secret from the Credentials tab.
Step 8: Deploy oauth2-proxy in Front of Prometheus
Generate a cookie secret
oauth2-proxy requires a random cookie secret for session encryption:
openssl rand -base64 32
oauth2-proxy configuration
Create an oauth2-proxy.cfg file:
provider = "oidc"
oidc_issuer_url = "https://auth.example.com/realms/monitoring"
client_id = "prometheus-proxy"
client_secret = "<paste-secret>"
cookie_secret = "<paste-generated-secret>"
cookie_secure = true
email_domains = ["*"]
upstreams = ["http://prometheus:9090"]
http_address = "0.0.0.0:4180"
redirect_url = "https://prometheus.example.com/oauth2/callback"
The email_domains = ["*"] setting allows any email domain, tighten this to your organization’s domain in production (e.g., email_domains = ["example.com"]). For role-based access control at the proxy layer, oauth2-proxy also supports allowed_groups which checks Keycloak group membership before granting access.
Docker Compose excerpt
services:
prometheus:
image: prom/prometheus:v2.52.0
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
# No ports exposed to the host — traffic goes only through oauth2-proxy
networks:
- monitoring
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
volumes:
- ./oauth2-proxy.cfg:/etc/oauth2-proxy.cfg
command: --config=/etc/oauth2-proxy.cfg
ports:
- "4180:4180"
networks:
- monitoring
networks:
monitoring:
Notice that Prometheus does not publish any ports to the host, only oauth2-proxy does. This means the Prometheus HTTP API is unreachable without first authenticating through Keycloak.
Your ingress or load balancer should terminate TLS and route prometheus.example.com to oauth2-proxy on port 4180. For an nginx or Traefik setup, the pattern is identical to the one described in the Keycloak HashiCorp Vault OIDC SSO guide, which also fronts an internal tool with an authenticating proxy.
Service-to-Service Scraping vs. Human UI Access
Protecting the Prometheus UI with oauth2-proxy secures human access. But Prometheus itself scrapes metrics from targets, those requests are machine-to-machine and do not go through the browser OAuth flow.
There are two common patterns for securing scrape targets:
TLS + bearer token: Prometheus supports a bearer_token or bearer_token_file field in each scrape config. You can issue a long-lived service account token from Keycloak using the client credentials grant and configure Prometheus to include it on every scrape request.
Mutual TLS (mTLS): For stronger guarantees, configure Prometheus and each exporter with client certificates. This removes the credential management problem entirely.
For the Prometheus UI itself, the oauth2-proxy approach already handles human sessions. For a full treatment of machine-to-machine authentication patterns, the SSO implementation guide for developers covers the client credentials flow in detail.
Frequently Asked Questions
How do I set up Grafana SSO with Keycloak?
Register a confidential Keycloak client for Grafana with the redirect URI set to /login/generic_oauth. Add a User Realm Role mapper to the client so Keycloak includes role names in the access token. Then configure the [auth.generic_oauth] section in grafana.ini (or equivalent environment variables) with the Keycloak realm’s auth, token, and userinfo URLs, your client credentials, and a role_attribute_path JMESPath expression that maps Keycloak role names to Grafana’s Admin, Editor, or Viewer org roles.
How do I map Keycloak roles to Grafana roles?
Create realm roles in Keycloak, for example, grafana-admin, grafana-editor, and grafana-viewer, and add a User Realm Role protocol mapper to the Grafana client so those roles appear in the token’s roles claim. In grafana.ini, set role_attribute_path to a JMESPath expression such as contains(roles[*], 'grafana-admin') && 'Admin' || contains(roles[*], 'grafana-editor') && 'Editor' || 'Viewer'. Grafana evaluates this expression against the token on every login and assigns the matching org role.
How do I add authentication to Prometheus?
Prometheus has no native authentication, you must place an authenticating proxy in front of it. oauth2-proxy is the standard choice: configure it with provider = oidc, point oidc_issuer_url at your Keycloak realm, and set upstreams to the Prometheus address. Run Prometheus without any exposed host ports so the only path to the metrics UI goes through oauth2-proxy. Users who visit the Prometheus URL are redirected to Keycloak, and only authenticated sessions are forwarded upstream.
Can I restrict Prometheus access to specific Keycloak groups?
Yes. oauth2-proxy supports an allowed_groups configuration option. Set it to a Keycloak group name, and oauth2-proxy will check the groups claim in the token before granting access. To include group membership in the token, add a Group Membership protocol mapper to the prometheus-proxy Keycloak client, configure it with the token claim name groups and set Multivalued to on. Users not in the specified group receive a 403 from oauth2-proxy and are never forwarded to Prometheus.
What happens if a Keycloak role is removed from a user?
Grafana re-evaluates the role_attribute_path expression on every login. If a user’s grafana-admin role is removed in Keycloak, the next time they log in their Grafana org role is downgraded to whatever the expression now returns. Existing sessions are not revoked instantly, Grafana honors the session until it expires or the user logs out. To reduce this window, lower the Grafana session lifetime or enable oauth_auto_login combined with short Keycloak session timeouts so re-authentication happens more frequently.
What’s Next
You now have a fully authenticated observability stack. Grafana enforces Keycloak-issued roles on every login, and Prometheus is reachable only through an oauth2-proxy session backed by Keycloak. Offboarding a team member means disabling their Keycloak account once, Grafana and Prometheus access disappears automatically at their next session boundary.
The same pattern extends to other internal tools: AlertManager, Jaeger, and similar services without native authentication can all sit behind oauth2-proxy with the same Keycloak client configuration. For the client scopes and token structures that make this work cleanly across many services, Keycloak client scopes vs roles explained covers how to design your realm so token claims stay manageable as the number of integrated tools grows.
If you would rather not manage Keycloak infrastructure yourself, Skycloak provides managed Keycloak hosting with automatic upgrades, built-in high availability, and monitoring, so you can focus on configuring your integrations rather than keeping the identity server running.

