mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Auth: Add support for OIDC RP-Initiated Logout (#70357)
* Fix signout redirect_uri issue * Fix signout redirect_uri issue * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * remove signout url global * style alignment * remove legacy handlers for devenv * Update pkg/api/login.go --------- Co-authored-by: Rao B V Chalapathi <b_v_chalapathi.rao@nokia.com> Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> Co-authored-by: jguer <me@jguer.space>
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
oauthkeycloak:
|
||||
image: quay.io/keycloak/keycloak:21.1
|
||||
container_name: oauthkeycloak
|
||||
command: --spi-login-protocol-openid-connect-legacy-logout-redirect-uri=true start-dev
|
||||
command: start-dev
|
||||
environment:
|
||||
KC_DB: postgres
|
||||
KC_DB_URL: jdbc:postgresql://oauthkeycloakdb/keycloak
|
||||
|
@@ -12,7 +12,7 @@
|
||||
oauthkeycloak:
|
||||
image: quay.io/keycloak/keycloak:22.0
|
||||
container_name: oauthkeycloak
|
||||
command: --spi-login-protocol-openid-connect-legacy-logout-redirect-uri=true start-dev
|
||||
command: start-dev
|
||||
environment:
|
||||
KC_DB: postgres
|
||||
KC_DB_URL: jdbc:postgresql://oauthkeycloakdb/keycloak
|
||||
|
@@ -11,7 +11,7 @@ Here is the conf you need to add to your configuration file (conf/custom.ini):
|
||||
|
||||
```ini
|
||||
[auth]
|
||||
signout_redirect_url = http://localhost:8087/realms/grafana/protocol/openid-connect/logout?redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Flogin
|
||||
signout_redirect_url = http://localhost:8087/realms/grafana/protocol/openid-connect/logout?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Flogin
|
||||
|
||||
[auth.generic_oauth]
|
||||
enabled = true
|
||||
|
@@ -901,7 +901,11 @@ Set to `true` to disable the signout link in the side menu. This is useful if yo
|
||||
|
||||
### signout_redirect_url
|
||||
|
||||
URL to redirect the user to after they sign out.
|
||||
The URL the user is redirected to upon signing out. To support [OpenID Connect RP-Initiated Logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html), the user must add `post_logout_redirect_uri` to the `signout_redirect_url`.
|
||||
|
||||
Example:
|
||||
|
||||
signout_redirect_url = http://localhost:8087/realms/grafana/protocol/openid-connect/logout?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Flogin
|
||||
|
||||
### oauth_auto_login
|
||||
|
||||
|
@@ -137,7 +137,7 @@ To enable Single Logout, you need to add the following option to the configurati
|
||||
|
||||
```ini
|
||||
[auth]
|
||||
signout_redirect_url = https://<PROVIDER_DOMAIN>/auth/realms/<REALM_NAME>/protocol/openid-connect/logout?redirect_uri=https%3A%2F%2F<GRAFANA_DOMAIN>%2Flogin
|
||||
signout_redirect_url = https://<PROVIDER_DOMAIN>/auth/realms/<REALM_NAME>/protocol/openid-connect/logout?post_logout_redirect_uri=https%3A%2F%2<GRAFANA_DOMAIN>%2Flogin
|
||||
```
|
||||
|
||||
As an example, `<PROVIDER_DOMAIN>` can be `keycloak-demo.grafana.org`,
|
||||
|
@@ -21,7 +21,6 @@ import (
|
||||
pref "github.com/grafana/grafana/pkg/services/preference"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
@@ -29,6 +28,8 @@ import (
|
||||
const (
|
||||
viewIndex = "index"
|
||||
loginErrorCookieName = "login_error"
|
||||
// #nosec G101 - this is not a hardcoded secret
|
||||
postLogoutRedirectParam = "post_logout_redirect_uri"
|
||||
)
|
||||
|
||||
var setIndexViewData = (*HTTPServer).setIndexViewData
|
||||
@@ -250,8 +251,20 @@ func (hs *HTTPServer) Logout(c *contextmodel.ReqContext) {
|
||||
}
|
||||
}
|
||||
|
||||
idTokenHint := ""
|
||||
oidcLogout := isPostLogoutRedirectConfigured(hs.Cfg.SignoutRedirectUrl)
|
||||
|
||||
// Invalidate the OAuth tokens in case the User logged in with OAuth or the last external AuthEntry is an OAuth one
|
||||
if entry, exists, _ := hs.oauthTokenService.HasOAuthEntry(c.Req.Context(), c.SignedInUser); exists {
|
||||
token := hs.oauthTokenService.GetCurrentOAuthToken(c.Req.Context(), c.SignedInUser)
|
||||
if oidcLogout {
|
||||
if token.Valid() {
|
||||
idTokenHint = token.Extra("id_token").(string)
|
||||
} else {
|
||||
hs.log.Warn("Token is not valid")
|
||||
}
|
||||
}
|
||||
|
||||
if err := hs.oauthTokenService.InvalidateOAuthTokens(c.Req.Context(), entry); err != nil {
|
||||
hs.log.Warn("failed to invalidate oauth tokens for user", "userId", c.UserID, "error", err)
|
||||
}
|
||||
@@ -264,8 +277,12 @@ func (hs *HTTPServer) Logout(c *contextmodel.ReqContext) {
|
||||
|
||||
authn.DeleteSessionCookie(c.Resp, hs.Cfg)
|
||||
|
||||
if setting.SignoutRedirectUrl != "" {
|
||||
c.Redirect(setting.SignoutRedirectUrl)
|
||||
rdUrl := hs.Cfg.SignoutRedirectUrl
|
||||
if rdUrl != "" {
|
||||
if oidcLogout {
|
||||
rdUrl = getPostRedirectUrl(hs.Cfg.SignoutRedirectUrl, idTokenHint)
|
||||
}
|
||||
c.Redirect(rdUrl)
|
||||
} else {
|
||||
hs.log.Info("Successful Logout", "User", c.Email)
|
||||
c.Redirect(hs.Cfg.AppSubURL + "/login")
|
||||
@@ -374,3 +391,38 @@ func getFirstPublicErrorMessage(err *errutil.Error) string {
|
||||
|
||||
return errPublic.Message
|
||||
}
|
||||
|
||||
func isPostLogoutRedirectConfigured(redirectUrl string) bool {
|
||||
if redirectUrl == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
u, err := url.Parse(redirectUrl)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
_, ok := q[postLogoutRedirectParam]
|
||||
return ok
|
||||
}
|
||||
|
||||
func getPostRedirectUrl(rdUrl string, tokenHint string) string {
|
||||
if tokenHint == "" {
|
||||
return rdUrl
|
||||
}
|
||||
if rdUrl == "" {
|
||||
return rdUrl
|
||||
}
|
||||
|
||||
u, err := url.Parse(rdUrl)
|
||||
if err != nil {
|
||||
return rdUrl
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
q.Set("id_token_hint", tokenHint)
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
@@ -98,7 +98,6 @@ var (
|
||||
LoginHint string
|
||||
PasswordHint string
|
||||
DisableSignoutMenu bool
|
||||
SignoutRedirectUrl string
|
||||
ExternalUserMngLinkUrl string
|
||||
ExternalUserMngLinkName string
|
||||
ExternalUserMngInfo string
|
||||
@@ -281,6 +280,7 @@ type Cfg struct {
|
||||
DisableLogin bool
|
||||
AdminEmail string
|
||||
DisableLoginForm bool
|
||||
SignoutRedirectUrl string
|
||||
// Not documented & not supported
|
||||
// stand in until a more complete solution is implemented
|
||||
AuthConfigUIAdminAccess bool
|
||||
@@ -1556,7 +1556,7 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
|
||||
}
|
||||
|
||||
cfg.OAuthCookieMaxAge = auth.Key("oauth_state_cookie_max_age").MustInt(600)
|
||||
SignoutRedirectUrl = valueAsString(auth, "signout_redirect_url", "")
|
||||
cfg.SignoutRedirectUrl = valueAsString(auth, "signout_redirect_url", "")
|
||||
// Deprecated
|
||||
cfg.OAuthSkipOrgRoleUpdateSync = auth.Key("oauth_skip_org_role_update_sync").MustBool(false)
|
||||
if cfg.OAuthSkipOrgRoleUpdateSync {
|
||||
|
Reference in New Issue
Block a user