Skip to content
Integrations

Pre-1.0 · Design-partner program

Grafana sign-in through Nauthera.

Grafana sign-in through Nauthera with generic OAuth: the OIDCClient, grafana.ini and Helm values, the client secret from its Secret, and roles by email.

By Michael Beutler, FounderPublished

Grafana signs people in through its generic OAuth provider, which works with any OpenID Connect issuer. With Nauthera the client is an OIDCClient in Grafana's namespace: the server generates the client secret, the operator writes it into a Secret there, and Grafana reads it from an environment variable, so the secret never appears in a values file. Roles map by email, since Nauthera emits no groups claim.

Checked against: Grafana 13.3.0, the latest release on November 4, 2026, its generic OAuth docs and configuration docs at that tag, and the Grafana Helm chart 14.3.1 in grafana-community/helm-charts, all read on November 4, 2026, and the OIDCClient definition in nauthera-server. This page has not recorded an end-to-end test against a running Grafana. To try it locally, start Nauthera with the Quick Start and add the client below.

1. The OIDCClient

apiVersion: nauthera.io/v1alpha1
kind: OIDCClient
metadata:
  name: grafana
  namespace: grafana
spec:
  clientId: grafana
  displayName: Grafana
  redirectUris:
    - https://grafana.example.com/login/generic_oauth
  postLogoutRedirectUris:
    - https://grafana.example.com/login
  grantTypes: [authorization_code, refresh_token]
  responseTypes: [code]
  scopes: [openid, profile, email, offline_access]
  tokenEndpointAuthMethod: client_secret_basic
  pkcePolicy: required_s256
  allowRefresh: true
  deletionPolicy: Retain
  secret:
    generate: true
    secretName: grafana-oidc

The grafana namespace has to be admitted by an organization: for the main one, list it in the chart value operator.mainOrganizationNamespaces. The operator then writes the Secret grafana-oidc with the keys client-id, client-secret and issuer. Those key names are not environment variable names Grafana reads, so the next step maps them.

2. Grafana's Helm values

grafana.ini:
  server:
    root_url: https://grafana.example.com
  auth.generic_oauth:
    enabled: true
    name: Nauthera
    client_id: grafana
    scopes: openid profile email offline_access
    auth_url: https://auth.example.com/authorize
    token_url: https://auth.example.com/token
    api_url: https://auth.example.com/userinfo
    use_pkce: true
    use_refresh_token: true
    login_attribute_path: preferred_username
    email_attribute_path: email
    name_attribute_path: name
    role_attribute_path: "email_verified && contains(['alice@example.com'], email) && 'Admin' || 'Viewer'"
    signout_redirect_url: https://auth.example.com/end_session?client_id=grafana&post_logout_redirect_uri=https%3A%2F%2Fgrafana.example.com%2Flogin
 
envValueFrom:
  GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET:
    secretKeyRef:
      name: grafana-oidc
      key: client-secret
  • The secret arrives as GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET, which overrides the same setting in grafana.ini. The chart renders envValueFrom as the container's valueFrom, so a secretKeyRef works there. Leave client_secret out of grafana.ini: the chart's assertNoLeakedSecrets check, on by default, fails the render when it is set to a value.
  • root_url decides the redirect URI Grafana sends, /login/generic_oauth under it, which has to match the registered one exactly.
  • use_pkce is needed: the client's pkcePolicy is required_s256.
  • use_refresh_token with offline_access lets Grafana renew access tokens, which Nauthera issues for 10 minutes by default.
  • The endpoints are the issuer's: for an organization other than the main one, put /orgs/<slug> after the host in all three.
  • tls_client_ca names a CA file to trust when Nauthera's certificate is not from a public CA, as in a closed network. Nauthera's Compose stack sets it for its local certificate.

Without the Helm chart, the same settings go in grafana.ini, with the Secret mounted as a file and read through Grafana's variable expansion:

[server]
root_url = https://grafana.example.com
 
[auth.generic_oauth]
enabled = true
name = Nauthera
client_id = grafana
client_secret = $__file{/etc/secrets/nauthera/client-secret}
scopes = openid profile email offline_access
auth_url = https://auth.example.com/authorize
token_url = https://auth.example.com/token
api_url = https://auth.example.com/userinfo
use_pkce = true
use_refresh_token = true
role_attribute_path = email_verified && contains(['alice@example.com'], email) && 'Admin' || 'Viewer'

3. Roles by email

Nauthera puts email and email_verified in the ID token when the email scope is granted, and Grafana evaluates role_attribute_path against the ID token first, then the userinfo response, then the access token. The expression above makes Alice an Admin only while her address is verified, and everyone else a Viewer. Add addresses to the list, or chain more &&/|| pairs for an Editor tier. For people from organizations brokered by yours, the ID token carries member_org_slug, and Grafana's org_attribute_path can map each member organization to a Grafana organization of its own; the identity broker docs show the expression.

4. Sign-out

signout_redirect_url sends the browser to Nauthera's end_session endpoint, which ends the Nauthera session and returns to /login, the URI the client registers in postLogoutRedirectUris. Because the URL carries post_logout_redirect_uri, Grafana adds the user's ID token as id_token_hint while its OAuth token is still valid (logout code). Without a valid ID token hint, Nauthera asks the user to confirm first; see Sessions, Consent & Logout.

When something goes wrong

What you seeWhyFix
helm template fails on auth.generic_oauth.client_secretassertNoLeakedSecrets found the secret in grafana.iniRemove it there; use envValueFrom
Nauthera refuses the redirect_uriroot_url differs from the registered hostSet server.root_url to the address in the browser
Nauthera answers invalid_scopeGrafana asks for a scope the client is not registered forMake scopes a subset of the client's spec.scopes
invalid_client after the Secret was deletedThe operator rotated the secret; the running pod still has the old oneRestart Grafana's pods; environment variables are read at start
Everyone is a Viewerrole_attribute_path matched nobodyCheck the email scope is granted and the address verified
Sign-in fails on the way back with a state errorGrafana's state cookie did not survive the return from another siteRun both on the same site; Nauthera's Compose stack, whose Grafana is on http://localhost:3000, sets cookie_samesite = none with cookie_secure = true
The OIDCClient shows NamespaceNotAdmittedNo organization admits grafanaAdmit the namespace (step 1)

Grafana's own docs ask for cookie_samesite = lax, its default, when signing in with OAuth, so change it only for the cross-site case above.

Release updates

Hear when the operator ships

One email when the operator ships. No newsletter.

By joining, you agree to our Privacy Policy and Terms.

Design partners

Apply for a design-partner seat

Five teams get calls with the founder, a say in the operator's design, and direct support when they pilot it.

Optional

We store your answers with your email address to select design partners. Privacy Policy