diff --git a/conf/defaults.ini b/conf/defaults.ini index 2ef2ad7942a..97c87edb8b2 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -335,6 +335,7 @@ tls_skip_verify_insecure = false tls_client_cert = tls_client_key = tls_client_ca = +send_client_credentials_via_post = false #################################### Basic Auth ########################## [auth.basic] diff --git a/conf/sample.ini b/conf/sample.ini index ba65727dc4b..473e4e8450c 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -284,6 +284,10 @@ log_queries = ;tls_client_key = ;tls_client_ca = +; Set to true to enable sending client_id and client_secret via POST body instead of Basic authentication HTTP header +; This might be required if the OAuth provider is not RFC6749 compliant, only supporting credentials passed via POST payload +;send_client_credentials_via_post = false + #################################### Grafana.com Auth #################### [auth.grafana_com] ;enabled = false diff --git a/docs/sources/auth/generic-oauth.md b/docs/sources/auth/generic-oauth.md index 6fa6531fc98..c3c44426ba7 100644 --- a/docs/sources/auth/generic-oauth.md +++ b/docs/sources/auth/generic-oauth.md @@ -17,7 +17,7 @@ can find examples using Okta, BitBucket, OneLogin and Azure. This callback URL must match the full HTTP address that you use in your browser to access Grafana, but with the prefix path of `/login/generic_oauth`. -You may have to set the `root_url` option of `[server]` for the callback URL to be +You may have to set the `root_url` option of `[server]` for the callback URL to be correct. For example in case you are serving Grafana behind a proxy. Example config: @@ -209,6 +209,17 @@ allowed_organizations = token_url = https://.my.centrify.com/OAuth2/Token/ ``` +## Set up OAuth2 with non-compliant providers + +Some OAuth2 providers might not support `client_id` and `client_secret` passed via Basic Authentication HTTP header, which +results in `invalid_client` error. To allow Grafana to authenticate via these type of providers, the client identifiers must be +send via POST body, which can be enabled via the following settings: + + ```bash + [auth.generic_oauth] + send_client_credentials_via_post = true + ``` +
diff --git a/pkg/setting/setting_oauth.go b/pkg/setting/setting_oauth.go index 93b1ab6f101..f0a3beccb44 100644 --- a/pkg/setting/setting_oauth.go +++ b/pkg/setting/setting_oauth.go @@ -1,20 +1,21 @@ package setting type OAuthInfo struct { - ClientId, ClientSecret string - Scopes []string - AuthUrl, TokenUrl string - Enabled bool - EmailAttributeName string - AllowedDomains []string - HostedDomain string - ApiUrl string - AllowSignup bool - Name string - TlsClientCert string - TlsClientKey string - TlsClientCa string - TlsSkipVerify bool + ClientId, ClientSecret string + Scopes []string + AuthUrl, TokenUrl string + Enabled bool + EmailAttributeName string + AllowedDomains []string + HostedDomain string + ApiUrl string + AllowSignup bool + Name string + TlsClientCert string + TlsClientKey string + TlsClientCa string + TlsSkipVerify bool + SendClientCredentialsViaPost bool } type OAuther struct { diff --git a/pkg/social/social.go b/pkg/social/social.go index 8918507f3b9..60099a028d6 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -63,28 +63,34 @@ func NewOAuthService() { for _, name := range allOauthes { sec := setting.Raw.Section("auth." + name) info := &setting.OAuthInfo{ - ClientId: sec.Key("client_id").String(), - ClientSecret: sec.Key("client_secret").String(), - Scopes: util.SplitString(sec.Key("scopes").String()), - AuthUrl: sec.Key("auth_url").String(), - TokenUrl: sec.Key("token_url").String(), - ApiUrl: sec.Key("api_url").String(), - Enabled: sec.Key("enabled").MustBool(), - EmailAttributeName: sec.Key("email_attribute_name").String(), - AllowedDomains: util.SplitString(sec.Key("allowed_domains").String()), - HostedDomain: sec.Key("hosted_domain").String(), - AllowSignup: sec.Key("allow_sign_up").MustBool(), - Name: sec.Key("name").MustString(name), - TlsClientCert: sec.Key("tls_client_cert").String(), - TlsClientKey: sec.Key("tls_client_key").String(), - TlsClientCa: sec.Key("tls_client_ca").String(), - TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(), + ClientId: sec.Key("client_id").String(), + ClientSecret: sec.Key("client_secret").String(), + Scopes: util.SplitString(sec.Key("scopes").String()), + AuthUrl: sec.Key("auth_url").String(), + TokenUrl: sec.Key("token_url").String(), + ApiUrl: sec.Key("api_url").String(), + Enabled: sec.Key("enabled").MustBool(), + EmailAttributeName: sec.Key("email_attribute_name").String(), + AllowedDomains: util.SplitString(sec.Key("allowed_domains").String()), + HostedDomain: sec.Key("hosted_domain").String(), + AllowSignup: sec.Key("allow_sign_up").MustBool(), + Name: sec.Key("name").MustString(name), + TlsClientCert: sec.Key("tls_client_cert").String(), + TlsClientKey: sec.Key("tls_client_key").String(), + TlsClientCa: sec.Key("tls_client_ca").String(), + TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(), + SendClientCredentialsViaPost: sec.Key("send_client_credentials_via_post").MustBool(), } if !info.Enabled { continue } + // handle the clients that do not properly support Basic auth headers and require passing client_id/client_secret via POST payload + if info.SendClientCredentialsViaPost { + oauth2.RegisterBrokenAuthHeaderProvider(info.TokenUrl) + } + if name == "grafananet" { name = grafanaCom }