OpenID Connect:不同身份提供者之间的刷新令牌行为不一致

问题描述 投票:0回答:2

我正在实现服务提供商,并且目前观察到不同的身份提供商在获取刷新令牌方面的不一致行为。我将在底部附加我的服务提供商golang代码,以防它对某人有所帮助或澄清我的问题。

我正在通过使用查询参数*/authn将登录请求重定向到access_type=offline端点来进行授权代码流。然后,第二步是在回调端点上接收授权代码,然后调用*/token端点以交换用于访问和刷新令牌的代码。

我已经尝试使用3个不同的身份提供者进行此流程,并发现以下结果:

  1. OneLoginhttps://openid-connect.onelogin.com/oidc):添加查询参数access_type=offline就足以接收刷新令牌。
  2. https://my-company.okta.com):添加access_type=offline是不够的。我需要在第一步(authn)中将offline_access添加到请求的Scopes参数中。此配置也适用于OneLogin!
  3. Google

  4. https://accounts.google.com):但是,对于Google,范围offline_access不被支持,并且返回了400 BAD REQUEST:
    某些请求的范围无效。 {valid = [openid,https://www.googleapis.com/auth/userinfo.profilehttps://www.googleapis.com/auth/userinfo.email],invalid = [offline_access]}

    与Google合作的唯一方法是从合并范围中删除offline_access,然后将查询参数prompt添加为值consent。但是,这不适用于Okta或OneLogin ...

    我是否缺少某些东西,还是应该为每个IdP提供自定义授权流程实现,以支持刷新令牌?

考虑到协议已完全规定,这似乎很奇怪。

package openidconnect import ( "context" "encoding/json" "net/http" "os" oidc "github.com/coreos/go-oidc" "golang.org/x/oauth2" ) var oidcClientID = getEnv("****", "OIDC_CLIENT_ID") var oidcClientSecret = getEnv("****", "OIDC_CLIENT_SECRET") var oidcProvider = getEnv("****", "OIDC_PROVIDER") var oidcLoginURI = "/v1/oidc_login" var oidcCallbackURI = "/v1/oidc_callback" var hostname = getEnv("http://localhost:8080", "HOSTNAME") func getEnv(defaultValue, key string) string { val := os.Getenv(key) if val == "" { return defaultValue } return val } //InitOpenIDConnect initiates open ID connect SSO func InitOpenIDConnect() error { ctx := context.Background() provider, err := oidc.NewProvider(ctx, oidcProvider) if err != nil { return err } // Configure an OpenID Connect aware OAuth2 client. oidcConfig := oauth2.Config{ ClientID: oidcClientID, ClientSecret: oidcClientSecret, RedirectURL: hostname + oidcCallbackURI, // Discovery returns the OAuth2 endpoints. Endpoint: provider.Endpoint(), // "openid" is a required scope for OpenID Connect flows. Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, // TODO: For Okta and OneLogin, add oidc.ScopeOfflineAccess Scope for refresh token. // Removed for now because Google API returns 400 when it is set. } handleOIDCLogin(&oidcConfig) handleOIDCCallback(provider, &oidcConfig) return nil } var approvalPromptOption = oauth2.SetAuthURLParam("prompt", "consent") func handleOIDCLogin(config *oauth2.Config) { state := "foobar" // Don't do this in production. http.HandleFunc(oidcLoginURI, func(w http.ResponseWriter, r *http.Request) { // approval prompt option is required for getting refresh token from Google API redirectURL := config.AuthCodeURL(state, oauth2.AccessTypeOffline, approvalPromptOption) http.Redirect(w, r, redirectURL, http.StatusFound) }) } func handleOIDCCallback(provider *oidc.Provider, config *oauth2.Config) { state := "foobar" // Don't do this in production. ctx := context.Background() http.HandleFunc(oidcCallbackURI, func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("state") != state { http.Error(w, "state did not match", http.StatusBadRequest) return } code := r.URL.Query().Get("code") oauth2Token, err := config.Exchange(ctx, code) if err != nil { http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError) return } tokenSource := config.TokenSource(ctx, oauth2Token) refreshedToken, err := tokenSource.Token() if err != nil { http.Error(w, "Failed to get refresh token: "+err.Error(), http.StatusInternalServerError) return } userInfo, err := provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token)) if err != nil { http.Error(w, "Failed to get userinfo: "+err.Error(), http.StatusInternalServerError) return } resp := struct { OAuth2Token *oauth2.Token UserInfo *oidc.UserInfo }{oauth2Token, userInfo} data, err := json.MarshalIndent(resp, "", " ") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write(data) }) }

我正在实现服务提供商,并且目前观察到不同的身份提供商在获取刷新令牌方面的不一致行为。我要附加我的服务提供商golang ...
openid-connect refresh-token google-openidconnect
2个回答
1
投票
不幸的是,我认为各个提供者对这一部分的实施方式有所不同。 Okta似乎是最符合这些要求的(要求offline_access,因为OIDC specification描述的是范围。)>

使作用域值可配置,并且还可能使配置自定义参数(例如access_type参数)成为避免为每个提供程序完全实现自定义实现的一种方法。


1
投票
这种问题确实非常普遍。抽象身份验证管道-我使用“身份验证器”接口或基类,然后在需要的地方专门化。只要管道与您的有价值的逻辑分开,我就会发现它运作良好。
© www.soinside.com 2019 - 2024. All rights reserved.