Missing Grafana oauth cookie

Deal all,

I have a strange behavior with Grafana and oauth (Keycloak), similar to other questions seen in this forum.
I have managed successfully to configure Grafana with oauth for Keycloak.
So when I try to access Grafana directly, it redirects me to Keycloak login, and after login, I am redirected correctly to Grafana.
During this process, some cookies are created, and the relevant one is named “oauth_state” (there is also a “grafana_session” cookie, I do not know if it is relevant).

I also have embedded some Grafana panels via iframe into a website, which uses the same oauth login.
The strange behavior is the following.

If, before going to the website, I visit Grafana, the cookie is created, and I am able to see the panels.
Conversely, if i go directly to my website and do the login, when I access the page with the iframes, I cannot see the Grafana panels, since the cookie is missing. The error I have is “login.OAuthLogin(missing saved state)” as in the other similar posts in this forum.

I have also tried to embed the whole Grafana in the website (just for testing), but the behavior is the same.

So i wonder if:

1- Is this a Grafana bug? Or am I missing something?
2- If this is an unsupported scenario/missing feature, is it possible to implement a workaround?

Btw, I am using Grafana 6.4.0
Thank you

Please include your Grafana configuration (strip out any sensitive information).

Have you configured allow_embedding?

Hi,

yes, I use allow_embedding.

To have a more complete picture: my Grafana instance is running behind a reverse proxy (Traefik),
but it works fine as long as embedding is not involved.
Embedding also works fine if:

  • As already said, I log into Grafana by accessing directly Grafana login and only after I visit the embedding page
  • Or if security is not involved (i.e. I disable access control, making Grafana accessible without login)

Here follow my grafana.ini:

##################### Grafana Configuration Example #####################

#

# Everything has defaults so you only need to uncomment things you want to

# change

# possible values : production, development

;app_mode = production

# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty

;instance_name = ${HOSTNAME}

#################################### Paths ####################################

[paths]

# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)

#

;data = /var/lib/grafana

#

# Directory where grafana can store logs

#

;logs = /var/log/grafana

#

# Directory where grafana will automatically scan and look for plugins

#

;plugins = /var/lib/grafana/plugins

provisioning = /etc/grafana/provisioning

#

#################################### Server ####################################

[server]

# Protocol (http or https)

;protocol = http

# The ip address to bind to, empty will bind to all interfaces

;http_addr =

# The http port to use

;http_port = 3000

# The public facing domain name used to access grafana from a browser

domain = DOMAIN_NAME

# Redirect to correct domain if host header does not match domain

# Prevents DNS rebinding attacks

;enforce_domain = false

# The full public facing url

root_url = https://%(domain)s/shared/grafana

# Log web requests

;router_logging = false

# the path relative working path

;static_root_path = public

# enable gzip

;enable_gzip = false

# https certs & key file

;cert_file =

;cert_key =

#################################### Database ####################################

[database]

# Either "mysql", "postgres" or "sqlite3", it's your choice

;type = sqlite3

;host = 127.0.0.1:3306

;name = grafana

;user = root

;password =

# For "postgres" only, either "disable", "require" or "verify-full"

;ssl_mode = disable

# For "sqlite3" only, path relative to data_path setting

;path = grafana.db

#################################### Session ####################################

[session]

# Either "memory", "file", "redis", "mysql", "postgres", default is "file"

;provider = file

# Provider config options

# memory: not have any config yet

# file: session dir path, is relative to grafana data_path

# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=grafana`

# mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1:3306)/database_name`

# postgres: user=a password=b host=localhost port=5432 dbname=c sslmode=disable

;provider_config = sessions

# Session cookie name

;cookie_name = grafana_sess

# If you use session in https only, default is false

;cookie_secure = false

# Session life time, default is 86400

;session_life_time = 86400

#################################### Analytics ####################################

[analytics]

# Server reporting, sends usage counters to stats.grafana.org every 24 hours.

# No ip addresses are being tracked, only simple counters to track

# running instances, dashboard and error counts. It is very helpful to us.

# Change this option to false to disable reporting.

;reporting_enabled = true

# Set to false to disable all checks to https://grafana.net

# for new vesions (grafana itself and plugins), check is used

# in some UI views to notify that grafana or plugin update exists

# This option does not cause any auto updates, nor send any information

# only a GET request to http://grafana.net to get latest versions

check_for_updates = true

# Google Analytics universal tracking code, only enabled if you specify an id here

;google_analytics_ua_id =

#################################### Security ####################################

[security]

# default admin user, created on startup

;admin_user = admin

admin_user = ADMIN

# default admin password, can be changed before first start of grafana, or in profile settings

admin_password = GRAFANA_PLAIN_PASSWORD

# used for signing

;secret_key = SW2YcwTIb9zpOOhoPsMm

# Auto-login remember days

;login_remember_days = 7

;cookie_username = grafana_user

;cookie_remember_name = grafana_remember

cookie_samesite = none

cookie_secure = true

allow_embedding = true

# disable gravatar profile images

;disable_gravatar = false

# data source proxy whitelist (ip_or_domain:port separated by spaces)

;data_source_proxy_whitelist =

[snapshots]

# snapshot sharing options

;external_enabled = true

;external_snapshot_url = https://snapshots-origin.raintank.io

;external_snapshot_name = Publish to snapshot.raintank.io

#################################### Users ####################################

[users]

# disable user signup / registration

;allow_sign_up = true

# Allow non admin users to create organizations

;allow_org_create = true

# Set to true to automatically assign new users to the default organization (id 1)

;auto_assign_org = true

# Default role new users will be automatically assigned (if disabled above is set to true)

;auto_assign_org_role = Viewer

# Background text for the user field on the login page

;login_hint = email or username

# Default UI theme ("dark" or "light")

;default_theme = dark

#################################### Auth General ##########################

[auth]

;disable_login_form = true

oauth_auto_login = true

signout_redirect_url = https://KEYCLOAK_DOMAIN_NAME/auth/realms/master/protocol/openid-connect/logout?redirect_uri=https://DOMAIN_NAME/shared/grafana

[auth.generic_oauth]

name = Oauth

enabled = true

;enabled = false

client_id = GRAFANA_CLIENT_ID

client_secret = GRAFANA_CLIENT_ISECRET

#scopes = email username

auth_url = https://KEYCLOAK_DOMAIN_NAME/auth/realms/master/protocol/openid-connect/auth

token_url = https://KEYCLOAK_DOMAIN_NAME/auth/realms/master/protocol/openid-connect/token

api_url = https://KEYCLOAK_DOMAIN_NAME/auth/realms/master/protocol/openid-connect/userinfo

;allowed_domains = mycompany.com mycompany.org

allow_sign_up = true

tls_skip_verify_insecure = true

#################################### Anonymous Auth ##########################

[auth.anonymous]

# enable anonymous access

enabled = false

# specify organization name that should be used for unauthenticated users

org_name = Main Org.

# specify role for unauthenticated users

org_role = Viewer

#################################### Github Auth ##########################

[auth.github]

;enabled = false

;allow_sign_up = false

;client_id = some_id

;client_secret = some_secret

;scopes = user:email,read:org

;auth_url = https://github.com/login/oauth/authorize

;token_url = https://github.com/login/oauth/access_token

;api_url = https://api.github.com/user

;team_ids =

;allowed_organizations =

#################################### Google Auth ##########################

[auth.google]

;enabled = false

;allow_sign_up = false

;client_id = some_client_id

;client_secret = some_client_secret

;scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email

;auth_url = https://accounts.google.com/o/oauth2/auth

;token_url = https://accounts.google.com/o/oauth2/token

;api_url = https://www.googleapis.com/oauth2/v1/userinfo

;allowed_domains =

#################################### Auth Proxy ##########################

[auth.proxy]

;enabled = false

;header_name = X-WEBAUTH-USER

;header_property = username

;auto_sign_up = true

#################################### Basic Auth ##########################

[auth.basic]

enabled = true

#################################### Auth LDAP ##########################

[auth.ldap]

;enabled = false

;config_file = /etc/grafana/ldap.toml

#################################### SMTP / Emailing ##########################

[smtp]

;enabled = false

;host = localhost:25

;user =

;password =

;cert_file =

;key_file =

;skip_verify = false

;from_address = admin@grafana.localhost

[emails]

;welcome_email_on_sign_up = false

#################################### Logging ##########################

[log]

# Either "console", "file", "syslog". Default is console and file

# Use space to separate multiple modes, e.g. "console file"

;mode = console, file

# Either "trace", "debug", "info", "warn", "error", "critical", default is "info"

;level = info

# For "console" mode only

[log.console]

;level =

# log line format, valid options are text, console and json

;format = console

# For "file" mode only

[log.file]

;level =

# log line format, valid options are text, console and json

;format = text

# This enables automated log rotate(switch of following options), default is true

;log_rotate = true

# Max line number of single file, default is 1000000

;max_lines = 1000000

# Max size shift of single file, default is 28 means 1 << 28, 256MB

;max_size_shift = 28

# Segment log daily, default is true

;daily_rotate = true

# Expired days of log file(delete after max days), default is 7

;max_days = 7

[log.syslog]

;level =

# log line format, valid options are text, console and json

;format = text

# Syslog network type and address. This can be udp, tcp, or unix. If left blank, the default unix endpoints will be used.

;network =

;address =

# Syslog facility. user, daemon and local0 through local7 are valid.

;facility =

# Syslog tag. By default, the process' argv[0] is used.

;tag =

#################################### AMQP Event Publisher ##########################

[event_publisher]

;enabled = false

;rabbitmq_url = amqp://localhost/

;exchange = grafana_events

#################################### Internal Grafana Metrics ##########################

# Metrics available at HTTP API Url /api/metrics

[metrics]

# Disable / Enable internal metrics

;enabled = true

# Publish interval

;interval_seconds = 10

# Send internal metrics to Graphite

; [metrics.graphite]

; address = localhost:2003

; prefix = prod.grafana.%(instance_name)s.

#################################### Internal Grafana Metrics ##########################

# Url used to to import dashboards directly from Grafana.net

#[grafana_net]

#url = https://grafana.net

#################################### Dashboard JSON files ##########################

#[dashboards.json]

#enabled = true

#path = /etc/grafana/dashboards

For embedding the panel via iframe, I do as follows:

<iframe frameborder="0" height="100" src="https://GRAFANA_HOST/shared/grafana/d-solo/SOEMTHING?orgId=1&amp;refresh=5s&amp;theme=light&amp;panelId=49" width="30%">

The website has the same SSO mechanism (i.e. keycloak).

Your configuration file looks a bit outdated, see https://github.com/grafana/grafana/blob/v6.4.0/conf/sample.ini. But still looks okay I think. Are you using Safari as browser - see https://grafana.com/docs/installation/requirements/#known-issues.

Hi,

no, I am using Firefox (under Windows 10 version 69.0.3 (64-bit), under ubuntu 18.04 version 69.0.2 (64-bit)).

Ok, I can update the config file, if it is useful to fix the issue. But I do not think it is the problem.

EDIT

I have tested the newer INI file, by uncommenting the options and doing copy-and-paste of values: no behavior changes :frowning:

Is this issue resolved ?same problem here when i use keycloak and grafana dashboard embedded.

Hi,

no, not solved :frowning: I actually did not work on this issue anymore, since it seems to me that this is a Grafana bug. After few time I opened this question, I also opened a bug report:

But it was closed, and marked as question: probably they do not think it is a Grafana issue.
If you will fill in a new issue, please let me know so I’ll stay updated (it still interests me).

Btw, in the meanwhile I am working on a custom and simple node.js reverse proxy, as a workaround:

  • Grafana will be behind the reverse proxy without authentication
  • The revere proxy will manage authentication

This is still not perfect, nor what we truly want, but it is better than nothing.

1.) Why we don’t see minimal, reproducible Grafana config example? How to reproduce issue?

2.) Very likely it is a problem with cookie SameSite config. What is configured? See 1.)

SameSite=Lax
Like None , but only send the cookie in a first-party context (meaning the URL in the address bar matches the cookie domain). Do not send it with the following cross-origin requests: non-GET, AJAX, iframe, image requests etc. It saves the user from cross-site request forgery.

3.) I really don’t recommend custom and simple node.js reverse proxy. It needs a bit of OIDC knowledge to implement it right. GitHub - louketo/louketo-proxy: A OpenID / Proxy service is IMHO the best option for this kind of setup.

I am facing same problem using google OAuth on firefox(76) running on Ubuntu(18.04) and on safari on Mac . Same problem appears in chrome(80) if run from localhost instead of domain. I have tried all the options of SameSite cookie config. Is there any way to resolve this?

Same problem appears in chrome(80) if run from localhost instead of domain. I have tried all the options of SameSite cookie config. Is there any way to resolve this? I’ve set it up allow_embedding = true and cookie_samesite = none

Hey,

I have the same issue but just with Okta, what should I add in my config to fix this :

[auth]
disable_login_form = true
oauth_auto_login = true
login_cookie_name = grafana_session
oauth_state_cookie_max_age = 60
[security]
cookie_secure = true
cookie_httponly = true
cookie_samesite = strict

Any help would be appreciated.

Thanks.

We are using Azure AD (Entra) as OAuth provider and everything was working fine until I disabled form authentication with username/password.
After that I was in a loop of login/login/login and in logs there was the message about cookie.

cookie_samesite=lax

fixed the issue