mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 19:12:19 +08:00
OAuth: Feature toggle for access token expiration check and docs (#58179)
* Add feature toggle for access token expiration check * Add docs for configuring refresh tokens * Update docs * Update docs based on review Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Improve documentation * Change access_type default to Offline * Update docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * Update pkg/services/featuremgmt/registry.go Co-authored-by: Eric Leijonmarck <eric.leijonmarck@gmail.com> * Regenerate toggles * Update Generic OAuth docs Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> Co-authored-by: Eric Leijonmarck <eric.leijonmarck@gmail.com>
This commit is contained in:
@ -169,6 +169,18 @@ GF_AUTH_AZUREAD_CLIENT_SECRET
|
||||
|
||||
**Note:** Verify that the Grafana [root_url]({{< relref "../../../configure-grafana/#root-url" >}}) is set in your Azure Application Redirect URLs.
|
||||
|
||||
### Configure refresh token
|
||||
|
||||
> Available in Grafana v9.3 and later versions.
|
||||
|
||||
> **Note:** This feature is behind the `accessTokenExpirationCheck` feature toggle.
|
||||
|
||||
When a user logs in using an OAuth provider, Grafana verifies that the access token has not expired. When an access token expires, Grafana uses the provided refresh token (if any exists) to obtain a new access token.
|
||||
|
||||
Grafana uses a refresh token to obtain a new access token without requiring the user to log in again. If a refresh token doesn't exist, Grafana logs the user out of the system after the access token has expired.
|
||||
|
||||
To enable a refresh token for AzureAD, extend the `scopes` in `[auth.azuread]` with `offline_access`.
|
||||
|
||||
### Configure allowed groups
|
||||
|
||||
To limit access to authenticated users who are members of one or more groups, set `allowed_groups`
|
||||
|
@ -116,9 +116,24 @@ use_pkce = true
|
||||
|
||||
Grafana always uses the SHA256 based `S256` challenge method and a 128 bytes (base64url encoded) code verifier.
|
||||
|
||||
### Configure refresh token
|
||||
|
||||
> Available in Grafana v9.3 and later versions.
|
||||
|
||||
> **Note:** This feature is behind the `accessTokenExpirationCheck` feature toggle.
|
||||
|
||||
When a user logs in using an OAuth provider, Grafana verifies that the access token has not expired. When an access token expires, Grafana uses the provided refresh token (if any exists) to obtain a new access token.
|
||||
|
||||
Grafana uses a refresh token to obtain a new access token without requiring the user to log in again. If a refresh token doesn't exist, Grafana logs the user out of the system after the access token has expired.
|
||||
|
||||
To configure Generic OAuth to use a refresh token, perform one or both of the following tasks, if required:
|
||||
|
||||
- Extend the `[auth.generic_oauth]` section with additional scopes
|
||||
- Enable the refresh token on the provider
|
||||
|
||||
## Set up OAuth2 with Auth0
|
||||
|
||||
1. Create a new Client in Auth0
|
||||
1. Use the following parameters to create a client in Auth0:
|
||||
|
||||
- Name: Grafana
|
||||
- Type: Regular Web Application
|
||||
@ -138,7 +153,7 @@ Grafana always uses the SHA256 based `S256` challenge method and a 128 bytes (ba
|
||||
name = Auth0
|
||||
client_id = <client id>
|
||||
client_secret = <client secret>
|
||||
scopes = openid profile email
|
||||
scopes = openid profile email offline_access
|
||||
auth_url = https://<domain>/authorize
|
||||
token_url = https://<domain>/oauth/token
|
||||
api_url = https://<domain>/userinfo
|
||||
@ -164,6 +179,8 @@ team_ids =
|
||||
allowed_organizations =
|
||||
```
|
||||
|
||||
By default, a refresh token is included in the response for the **Authorization Code Grant**.
|
||||
|
||||
## Set up OAuth2 with Centrify
|
||||
|
||||
1. Create a new Custom OpenID Connect application configuration in the Centrify dashboard.
|
||||
@ -195,6 +212,8 @@ allowed_organizations =
|
||||
api_url = https://<your domain>.my.centrify.com/OAuth2/UserInfo/<Application ID>
|
||||
```
|
||||
|
||||
By default, a refresh token is included in the response for the **Authorization Code Grant**.
|
||||
|
||||
## Set up OAuth2 with OneLogin
|
||||
|
||||
1. Create a new Custom Connector with the following settings:
|
||||
|
@ -64,6 +64,14 @@ automatically signed up.
|
||||
|
||||
You can also use [variable expansion]({{< relref "../../../configure-grafana/#variable-expansion" >}}) to reference environment variables and local files in your GitHub auth configuration.
|
||||
|
||||
### GitHub refresh token
|
||||
|
||||
> Available in Grafana v9.3 and later versions.
|
||||
|
||||
> **Note:** This feature is behind the `accessTokenExpirationCheck` feature toggle.
|
||||
|
||||
GitHub OAuth applications do not support refresh tokens because the provided access tokens do not expire.
|
||||
|
||||
### team_ids
|
||||
|
||||
Require an active team membership for at least one of the given teams on
|
||||
|
@ -81,6 +81,18 @@ to login on your Grafana instance.
|
||||
You can limit access to only members of a given group or list of
|
||||
groups by setting the `allowed_groups` option.
|
||||
|
||||
### Configure refresh token
|
||||
|
||||
> Available in Grafana v9.3 and later versions.
|
||||
|
||||
> **Note:** This feature is behind the `accessTokenExpirationCheck` feature toggle.
|
||||
|
||||
When a user logs in using an OAuth provider, Grafana verifies that the access token has not expired. When an access token expires, Grafana uses the provided refresh token (if any exists) to obtain a new access token.
|
||||
|
||||
Grafana uses a refresh token to obtain a new access token without requiring the user to log in again. If a refresh token doesn't exist, Grafana logs the user out of the system after the access token has expired.
|
||||
|
||||
By default, GitLab provides a refresh token.
|
||||
|
||||
### allowed_groups
|
||||
|
||||
To limit access to authenticated users that are members of one or more [GitLab
|
||||
|
@ -53,3 +53,15 @@ You may allow users to sign-up via Google authentication by setting the
|
||||
`allow_sign_up` option to `true`. When this option is set to `true`, any
|
||||
user successfully authenticating via Google authentication will be
|
||||
automatically signed up.
|
||||
|
||||
### Configure refresh token
|
||||
|
||||
> Available in Grafana v9.3 and later versions.
|
||||
|
||||
> **Note:** This feature is behind the `accessTokenExpirationCheck` feature toggle.
|
||||
|
||||
When a user logs in using an OAuth provider, Grafana verifies that the access token has not expired. When an access token expires, Grafana uses the provided refresh token (if any exists) to obtain a new access token.
|
||||
|
||||
Grafana uses a refresh token to obtain a new access token without requiring the user to log in again. If a refresh token doesn't exist, Grafana logs the user out of the system after the access token has expired.
|
||||
|
||||
By default, Grafana includes the `access_type=offline` parameter in the authorization request to request a refresh token.
|
||||
|
@ -141,3 +141,15 @@ Grafana also assigns the user the `Admin` role of the default organization.
|
||||
role_attribute_path = contains(roles[*], 'grafanaadmin') && 'GrafanaAdmin' || contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer'
|
||||
allow_assign_grafana_admin = true
|
||||
```
|
||||
|
||||
### Configure refresh token
|
||||
|
||||
> Available in Grafana v9.3 and later versions.
|
||||
|
||||
> **Note:** This feature is behind the `accessTokenExpirationCheck` feature toggle.
|
||||
|
||||
When a user logs in using an OAuth provider, Grafana verifies that the access token has not expired. When an access token expires, Grafana uses the provided refresh token (if any exists) to obtain a new access token.
|
||||
|
||||
Grafana uses a refresh token to obtain a new access token without requiring the user to log in again. If a refresh token doesn't exist, Grafana logs the user out of the system after the access token has expired.
|
||||
|
||||
To enable a refresh token for Keycloak, extend the `scopes` in `[auth.generic_oauth]` with `offline_access`.
|
||||
|
@ -54,6 +54,19 @@ allowed_groups =
|
||||
role_attribute_path =
|
||||
```
|
||||
|
||||
### Configure refresh token
|
||||
|
||||
> Available in Grafana v9.3 and later versions.
|
||||
|
||||
> **Note:** This feature is behind the `accessTokenExpirationCheck` feature toggle.
|
||||
|
||||
When a user logs in using an OAuth provider, Grafana verifies that the access token has not expired. When an access token expires, Grafana uses the provided refresh token (if any exists) to obtain a new access token.
|
||||
|
||||
Grafana uses a refresh token to obtain a new access token without requiring the user to log in again. If a refresh token doesn't exist, Grafana logs the user out of the system after the access token has expired.
|
||||
|
||||
1. To enable the `Refresh Token`, grant type in the `General Settings` section.
|
||||
1. Extend the `scopes` in `[auth.okta]` with `offline_access`.
|
||||
|
||||
### Configure allowed groups and domains
|
||||
|
||||
To limit access to authenticated users that are members of one or more groups, set `allowed_groups`
|
||||
|
@ -80,5 +80,6 @@ export interface FeatureToggles {
|
||||
datasourceLogger?: boolean;
|
||||
accessControlOnCall?: boolean;
|
||||
nestedFolders?: boolean;
|
||||
accessTokenExpirationCheck?: boolean;
|
||||
elasticsearchBackendMigration?: boolean;
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHa
|
||||
authProxy := authproxy.ProvideAuthProxy(cfg, remoteCacheSvc, loginservice.LoginServiceMock{}, &usertest.FakeUserService{}, sqlStore)
|
||||
loginService := &logintest.LoginServiceFake{}
|
||||
authenticator := &logintest.AuthenticatorFake{}
|
||||
ctxHdlr := contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, sqlStore, tracer, authProxy, loginService, nil, authenticator, usertest.NewUserServiceFake(), orgtest.NewOrgServiceFake(), nil)
|
||||
ctxHdlr := contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, sqlStore, tracer, authProxy, loginService, nil, authenticator, usertest.NewUserServiceFake(), orgtest.NewOrgServiceFake(), nil, featuremgmt.WithFeatures())
|
||||
|
||||
return ctxHdlr
|
||||
}
|
||||
|
@ -97,7 +97,8 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
||||
|
||||
code := ctx.Query("code")
|
||||
if code == "" {
|
||||
opts := []oauth2.AuthCodeOption{oauth2.AccessTypeOnline}
|
||||
// FIXME: access_type is a Google OAuth2 specific thing, consider refactoring this and moving to google_oauth.go
|
||||
opts := []oauth2.AuthCodeOption{oauth2.AccessTypeOffline}
|
||||
|
||||
if provider.UsePKCE {
|
||||
ascii, pkce, err := genPKCECode()
|
||||
|
@ -30,6 +30,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler/authproxy"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||
"github.com/grafana/grafana/pkg/services/login/logintest"
|
||||
"github.com/grafana/grafana/pkg/services/navtree"
|
||||
@ -832,7 +833,7 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg, mockSQLStore *dbtest.Fake
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
authProxy := authproxy.ProvideAuthProxy(cfg, remoteCacheSvc, loginService, userService, mockSQLStore)
|
||||
authenticator := &logintest.AuthenticatorFake{ExpectedUser: &user.User{}}
|
||||
return contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, mockSQLStore, tracer, authProxy, loginService, apiKeyService, authenticator, userService, orgService, oauthTokenService)
|
||||
return contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, mockSQLStore, tracer, authProxy, loginService, apiKeyService, authenticator, userService, orgService, oauthTokenService, featuremgmt.WithFeatures(featuremgmt.FlagAccessTokenExpirationCheck))
|
||||
}
|
||||
|
||||
type fakeRenderService struct {
|
||||
|
@ -104,7 +104,7 @@ func getContextHandler(t *testing.T) *ContextHandler {
|
||||
|
||||
return ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc,
|
||||
renderSvc, sqlStore, tracer, authProxy, loginService, nil, authenticator,
|
||||
&userService, orgService, nil)
|
||||
&userService, orgService, nil, nil)
|
||||
}
|
||||
|
||||
type FakeGetSignUserStore struct {
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler/authproxy"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/oauthtoken"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@ -46,7 +47,7 @@ func ProvideService(cfg *setting.Cfg, tokenService models.UserTokenService, jwtS
|
||||
remoteCache *remotecache.RemoteCache, renderService rendering.Service, sqlStore db.DB,
|
||||
tracer tracing.Tracer, authProxy *authproxy.AuthProxy, loginService login.Service,
|
||||
apiKeyService apikey.Service, authenticator loginpkg.Authenticator, userService user.Service,
|
||||
orgService org.Service, oauthTokenService oauthtoken.OAuthTokenService,
|
||||
orgService org.Service, oauthTokenService oauthtoken.OAuthTokenService, features *featuremgmt.FeatureManager,
|
||||
) *ContextHandler {
|
||||
return &ContextHandler{
|
||||
Cfg: cfg,
|
||||
@ -63,6 +64,7 @@ func ProvideService(cfg *setting.Cfg, tokenService models.UserTokenService, jwtS
|
||||
userService: userService,
|
||||
orgService: orgService,
|
||||
oauthTokenService: oauthTokenService,
|
||||
features: features,
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +84,7 @@ type ContextHandler struct {
|
||||
userService user.Service
|
||||
orgService org.Service
|
||||
oauthTokenService oauthtoken.OAuthTokenService
|
||||
features *featuremgmt.FeatureManager
|
||||
// GetTime returns the current time.
|
||||
// Stubbable by tests.
|
||||
GetTime func() time.Time
|
||||
@ -445,6 +448,7 @@ func (h *ContextHandler) initContextWithToken(reqContext *models.ReqContext, org
|
||||
getTime = time.Now
|
||||
}
|
||||
|
||||
if h.features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) {
|
||||
// Check whether the logged in User has a token (whether the User used an OAuth provider to login)
|
||||
oauthToken, exists, _ := h.oauthTokenService.HasOAuthEntry(ctx, queryResult)
|
||||
if exists {
|
||||
@ -471,6 +475,7 @@ func (h *ContextHandler) initContextWithToken(reqContext *models.ReqContext, org
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reqContext.SignedInUser = queryResult
|
||||
reqContext.IsSignedIn = true
|
||||
|
@ -357,6 +357,11 @@ var (
|
||||
State: FeatureStateAlpha,
|
||||
RequiresDevMode: true,
|
||||
},
|
||||
{
|
||||
Name: "accessTokenExpirationCheck",
|
||||
Description: "Enable OAuth access_token expiration check and token refresh using the refresh_token",
|
||||
State: FeatureStateStable,
|
||||
},
|
||||
{
|
||||
Name: "elasticsearchBackendMigration",
|
||||
Description: "Use Elasticsearch as backend data source",
|
||||
|
@ -263,6 +263,10 @@ const (
|
||||
// Enable folder nesting
|
||||
FlagNestedFolders = "nestedFolders"
|
||||
|
||||
// FlagAccessTokenExpirationCheck
|
||||
// Enable OAuth access_token expiration check and token refresh using the refresh_token
|
||||
FlagAccessTokenExpirationCheck = "accessTokenExpirationCheck"
|
||||
|
||||
// FlagElasticsearchBackendMigration
|
||||
// Use Elasticsearch as backend data source
|
||||
FlagElasticsearchBackendMigration = "elasticsearchBackendMigration"
|
||||
|
Reference in New Issue
Block a user