This document describes how to integrate Grafana with Keycloak using OpenID Connect (OIDC). The setup is suitable for Kubernetes deployments where TLS is terminated externally (e.g. via HAProxy or an ingress controller).

This setup enables:
grafanaAssuming Grafana is exposed at:
https://grafana.example.net
Set the following:
Valid Redirect URIs
https://grafana.example.net/login/generic_oauth
Valid Post Logout Redirect URIs (optional)
https://grafana.example.net/
Web Origins
https://grafana.example.net
(or + during testing)
Ensure these claims are present in the ID token:
| Claim | Source | Notes |
|---|---|---|
preferred_username |
User property | Required for login |
email |
User property | Optional but recommended |
groups |
Group membership | Used for role mapping |
Grafana must generate external URLs that match the public address. This is mandatory for OAuth.
- name: GF_SERVER_ROOT_URL
value: "https://grafana.example.net"
For TLS termination at HAProxy or ingress:
- name: GF_SERVER_PROTOCOL
value: "http"
- name: GF_SERVER_HTTP_PORT
value: "3000"
- name: GF_AUTH_GENERIC_OAUTH_ENABLED
value: "true"
- name: GF_AUTH_GENERIC_OAUTH_NAME
value: "Keycloak"
- name: GF_AUTH_GENERIC_OAUTH_CLIENT_ID
value: "grafana"
- name: GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET
value: "<client-secret>"
- name: GF_AUTH_GENERIC_OAUTH_SCOPES
value: "openid profile email"
- name: GF_AUTH_GENERIC_OAUTH_AUTH_URL
value: "https://keycloak.example.net/realms/<REALM>/protocol/openid-connect/auth"
- name: GF_AUTH_GENERIC_OAUTH_TOKEN_URL
value: "https://keycloak.example.net/realms/<REALM>/protocol/openid-connect/token"
- name: GF_AUTH_GENERIC_OAUTH_API_URL
value: "https://keycloak.example.net/realms/<REALM>/protocol/openid-connect/userinfo"
Example mapping using Keycloak groups:
- name: GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH
value: "contains(groups[*], 'grafana-admin') && 'Admin' || 'Viewer'"
To allow OAuth users to become Grafana Admins:
- name: GF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_GRAFANA_ADMIN
value: "true"
Invalid parameter: redirect_uriCause:
GF_SERVER_ROOT_URL not setResolution:
GF_SERVER_ROOT_URLDisable local logins after validation:
- name: GF_AUTH_DISABLE_LOGIN_FORM
value: "true"
Above is described a way very often used utilizing variables as key-value pairs in the deployment section. Personally I prefer not to use that, as it pretty much clutters the manifest. Here is a better way of doing that:
---
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-config
namespace: grafana
labels:
app: grafana
data:
GF_SMTP_ENABLED: "true"
GF_SMTP_HOST: "<host>:25"
GF_SMTP_SKIP_VERIFY: "true"
GF_SMTP_FROM_ADDRESS: "<emailaddress>"
GF_SMTP_FROM_NAME: "Grafana"
GF_SERVER_ROOT_URL: "<root_url>"
GF_SERVER_DOMAIN: "<domain>"
GF_SERVER_ENFORCE_DOMAIN: "true"
GF_SERVER_PROTOCOL: "http"
GF_SERVER_HTTP_PORT: "3000"
GF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_GRAFANA_ADMIN: "true"
GF_AUTH_DISABLE_LOGIN_FORM: "true"
Then within the deployment, the config map simply is referenced:
containers:
- name: grafana
image: grafana/grafana
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: grafana-config
But of course this is all up to whom it conerns.