I’ve recently returned to self-hosting — one of those periodic relapses where you get to re-solve old problems by testing different approaches to things you already “solved” once before1. This time it was monitoring’s turn.
I was surprised that right after setting up OIDC I couldn’t add a single dashboard in Grafana — login worked, yet permissions were missing.
Setup
Grafana already sat behind Authelia (self-hosted SSO, single user, in the admins group) via native OIDC2 (auth.generic_oauth). The task seemed trivial: everyone in the admins group should automatically get the GrafanaAdmin role in Grafana on login, instead of assigning it manually in the UI — which would get overwritten on every login anyway, since allow_sign_up: true re-provisions the user each time.
Grafana
The standard, documented path: groups scope on the OIDC client + role_attribute_path (a JMESPath3 expression) on Grafana’s side.
I provision Grafana via Ansible, using the grafana.grafana.grafana role. The key piece of config is the grafana_ini_oauth variable:
grafana_ini_oauth:
"auth.generic_oauth":
enabled: true
name: Authelia
client_id: grafana
client_secret: "{{ grafana_authelia_oidc_secret | default('') }}"
scopes: "openid profile email groups"
auth_url: "https://authelia.example.com/api/oidc/authorization"
token_url: "https://authelia.example.com/api/oidc/token"
api_url: "https://authelia.example.com/api/oidc/userinfo"
use_pkce: true
allow_sign_up: true
role_attribute_path: "contains(groups[*], 'admins') && 'GrafanaAdmin' || 'Viewer'"
The admin user is the only member of the admins group in Authelia — hence contains(groups[*], 'admins') in the expression above. A necessary condition for this to work: the groups scope must be added to Grafana’s OIDC client, just like in the config above.
What was supposed to happen
Grafana’s docs for role_attribute_path describe a three-tier fallback: first id_token, if nothing’s found there — userinfo (api_url), if still nothing — access_token. Sounds like solid redundancy.
What happened in practice
In Grafana 13.2.1 the fallback to userinfo is broken — confirmed by grafana/grafana#106686. If the claim isn’t in id_token, Grafana doesn’t check userinfo the way the docs claim it does.
Meanwhile, since version 4.39 Authelia stopped putting scope claims (like groups) into id_token by default — they only land in userinfo. That’s a sensible security tightening on its own (less PII in a token that ends up on various sites), but combined with Grafana’s broken fallback it produces a silent blackout: exactly the claim that matters for the role never arrives anywhere.4
The fix on Authelia’s side
identity_providers.oidc.claims_policies on Authelia’s side — explicitly forces specific claims back into id_token per-client, referenced from the client via claims_policy: <name>.
identity_providers:
oidc:
# ...
claims_policies:
grafana: # policy name, arbitrary, referenced below
id_token:
- 'groups' # force this claim into the id_token
clients:
- client_id: 'other'
# no claims_policy — doesn't get this treatment
- client_id: 'grafana'
client_secret: '...'
claims_policy: 'grafana' # attach the policy to the client
redirect_uris:
- 'https://grafana.example.com/login/generic_oauth'
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups' # must be requested, otherwise there's nothing to force
With these settings, logging into Grafana as admin works again — another old problem checked off, solved anew and differently than last time.
And, having gained the ability to add a dashboard, did I add one?

Footnotes
-
RPi-Monitor, never forget ↩
-
OIDC (OpenID Connect) — a login (SSO) protocol built on top of OAuth2. ↩
-
JMESPath — a query language for extracting and transforming data from JSON documents, used here to pull groups out of the token claims. ↩
-
Who’s spent ages figuring out how to add a new dashboard, only to eventually realize they’re stuck with a Viewer role? Author of this post of course. ↩