Grafana OAuth with Authentik: The root_url Gotcha

Tags: writeups grafana authentik sso oauth identity zero-trust observability oidc


The Setup

Grafana was running on Node-B at 192.168.20.40:3000. Authentik was running on Home One at 192.168.20.10:9000. The goal: SSO login for Grafana through Authentik using OIDC, so users authenticate once via Authentik (with MFA) and land in Grafana without separate credentials.


The Authentik Side

Create the OAuth2 Provider

In Authentik → Applications → Providers → Create:

Type:               OAuth2/OpenID Connect
Name:               Grafana
Client Type:        Confidential
Redirect URIs:      http://192.168.20.40:3000/login/generic_oauth
Signing Key:        authentik Self-signed Certificate

Authentik generates a Client ID and Client Secret. Copy both - they go into Grafana's config.

Create the Application

In Authentik → Applications → Create:

Name:       Grafana
Slug:       grafana
Provider:   Grafana (the provider just created)
Launch URL: http://192.168.20.40:3000

The Grafana Side

Edit /etc/grafana/grafana.ini:

[auth.generic_oauth]
enabled = true
name = Authentik
client_id = YOUR_CLIENT_ID
client_secret = YOUR_CLIENT_SECRET
scopes = openid profile email
auth_url = http://192.168.20.10:9000/application/o/authorize/
token_url = http://192.168.20.10:9000/application/o/token/
api_url = http://192.168.20.10:9000/application/o/userinfo/
role_attribute_path = contains(groups[*], 'homelab-admins') && 'Admin' || 'Viewer'
allow_sign_up = true

Restart Grafana:

systemctl restart grafana-server

Load the Grafana login page. There's now a "Login with Authentik" button. Click it.


The Error

redirect_uri_mismatch
The redirect URI included is not valid.

Authentik rejected the OAuth callback because the redirect_uri parameter in the request didn't match any of the registered redirect URIs in the provider configuration.


The Debugging

What I Checked First

  1. Redirect URI in Authentik - Set to http://192.168.20.40:3000/login/generic_oauth. Correct.
  2. Client ID/Secret - Copy-pasted directly from Authentik. Correct.
  3. auth_url / token_url / api_url - All pointing to the right Authentik endpoints. Correct.

Everything looked right. The error persisted.

What I Checked Second

Opened browser developer tools and inspected the OAuth redirect:

GET http://192.168.20.10:9000/application/o/authorize/
  ?client_id=xxx
  &redirect_uri=http://localhost:3000/login/generic_oauth
  &response_type=code
  &scope=openid+profile+email

There it is: redirect_uri=http://localhost:3000/...

Grafana was sending localhost as the redirect URI, not 192.168.20.40. Authentik's provider had 192.168.20.40 registered. Mismatch. Denied.


The Root Cause

Grafana constructs the OAuth redirect_uri based on its [server] section configuration. Specifically, it uses the root_url setting. When root_url is not explicitly set, Grafana defaults to:

[server]
root_url = http://localhost:3000/

This means every OAuth redirect Grafana generates uses localhost as the hostname - regardless of what IP or domain you actually use to access it.


The Fix

Explicitly set root_url in Grafana's [server] section:

[server]
domain = 192.168.20.40
root_url = http://192.168.20.40:3000/

Restart:

systemctl restart grafana-server

Now the OAuth redirect sends:

redirect_uri=http://192.168.20.40:3000/login/generic_oauth

This matches the registered URI in Authentik. Login works. MFA challenge appears (enforced at the Authentik flow level). After authentication, Grafana receives the user info and creates/maps the account.


Why This Is a Gotcha

The root_url setting is in the [server] section, not the [auth.generic_oauth] section. When you're configuring OAuth, you're looking at the [auth] block. The [server] section feels unrelated - it's about the HTTP server, not authentication.

But OAuth is an HTTP redirect dance. The redirect URI is constructed from the server's own understanding of its hostname. If the server thinks it's localhost, every OAuth redirect says localhost.

The error message from the identity provider - redirect_uri_mismatch - sends you looking at the OAuth configuration. You check the redirect URI in Authentik, you check the auth_url in Grafana, you check the client credentials. Everything matches. The actual problem is a commented-out line three sections above your auth config.

The Specific Trap

In a fresh Grafana install, grafana.ini has root_url commented out:

;root_url = %(protocol)s://%(domain)s:%(http_port)s/

The semicolon means "use the default." The default is localhost. If you're scanning the config looking for misconfiguration, a commented-out line looks intentional - it looks like Grafana is using sensible defaults. It isn't.


The Complete Working Configuration

[server]
protocol = http
http_addr = 0.0.0.0
http_port = 3000
domain = 192.168.20.40
root_url = http://192.168.20.40:3000/

[auth.generic_oauth]
enabled = true
name = Authentik
client_id = YOUR_CLIENT_ID
client_secret = YOUR_CLIENT_SECRET
scopes = openid profile email
auth_url = http://192.168.20.10:9000/application/o/authorize/
token_url = http://192.168.20.10:9000/application/o/token/
api_url = http://192.168.20.10:9000/application/o/userinfo/
role_attribute_path = contains(groups[*], 'homelab-admins') && 'Admin' || 'Viewer'
allow_sign_up = true
auto_login = false

If Using NPM (HTTPS)

When accessing Grafana through https://grafana.tima.dev, update root_url to match:

[server]
domain = grafana.tima.dev
root_url = https://grafana.tima.dev/

And update the Authentik provider's redirect URI to match:

https://grafana.tima.dev/login/generic_oauth

The root_url must always match exactly how users access Grafana - including protocol, hostname, and port.


Role Mapping

The role_attribute_path setting maps Authentik groups to Grafana roles using JMESPath:

role_attribute_path = contains(groups[*], 'homelab-admins') && 'Admin' || 'Viewer'

This means:

  • Users in the homelab-admins Authentik group → Grafana Admin
  • Everyone else → Grafana Viewer

Authentik groups must be included in the OIDC token's claims. In the Authentik provider settings, ensure the groups scope is included and mapped.


Onboarding Checklist

After hitting this gotcha, I created a checklist for adding any new service to Authentik SSO:

□ Create Authentik provider (OAuth2/OIDC)
□ Set redirect URI to EXACTLY how users access the service
□ Create Authentik application linked to provider
□ Configure service with client_id, client_secret, auth URLs
□ Set the service's root_url / base_url / external_url to match
□ Test from a private/incognito window (no cached sessions)
□ Verify MFA challenge appears (Authentik flow enforcement)
□ Check role/group mapping works correctly

Step 5 is the one that catches people. Every service has some version of root_url - Grafana calls it root_url, other services call it base_url, external_url, public_url, or server_url. Whatever it's called, it must match the redirect URI registered in the identity provider.


Related: Post 016 - Authentik Full Integration Playbook | Post 017 - TIG Stack Observability | Post 014 - Tailscale Remote Access