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

1 Like

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 Like

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.

1 Like

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.

1 Like

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