diff --git a/cmd/example-app/main.go b/cmd/example-app/main.go index 56d1a2c8..3e6c7006 100644 --- a/cmd/example-app/main.go +++ b/cmd/example-app/main.go @@ -30,6 +30,10 @@ type app struct { verifier *oidc.IDTokenVerifier provider *oidc.Provider + // Does the provider use "offline_access" scope to request a refresh token + // or does it use "access_type=offline" (e.g. Google)? + offlineAsScope bool + ctx context.Context cancel context.CancelFunc } @@ -102,6 +106,34 @@ func cmd() *cobra.Command { if err != nil { return fmt.Errorf("Failed to query provider %q: %v", issuerURL, err) } + + var s struct { + // What scopes does a provider support? + // + // See: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata + ScopesSupported []string `json:"scopes_supported"` + } + if err := provider.Claims(&s); err != nil { + return fmt.Errorf("Failed to parse provider scopes_supported: %v", err) + } + + if len(s.ScopesSupported) == 0 { + // scopes_supported is a "RECOMMENDED" discovery claim, not a required + // one. If missing, assume that the provider follows the spec and has + // an "offline_access" scope. + a.offlineAsScope = true + } else { + // See if scopes_supported has the "offline_access" scope. + a.offlineAsScope = func() bool { + for _, scope := range s.ScopesSupported { + if scope == oidc.ScopeOfflineAccess { + return true + } + } + return false + }() + } + a.provider = provider a.verifier = provider.NewVerifier(a.ctx, oidc.VerifyAudience(a.clientID)) @@ -166,10 +198,15 @@ func (a *app) handleLogin(w http.ResponseWriter, r *http.Request) { scopes = append(scopes, "audience:server:client_id:"+client) } - // TODO(ericchiang): Determine if provider does not support "offline_access" or has - // some other mechanism for requesting refresh tokens. - scopes = append(scopes, "openid", "profile", "email", "offline_access") - http.Redirect(w, r, a.oauth2Config(scopes).AuthCodeURL(""), http.StatusSeeOther) + authCodeURL := "" + scopes = append(scopes, "openid", "profile", "email") + if a.offlineAsScope { + scopes = append(scopes, "offline_access") + authCodeURL = a.oauth2Config(scopes).AuthCodeURL("") + } else { + authCodeURL = a.oauth2Config(scopes).AuthCodeURL("", oauth2.AccessTypeOffline) + } + http.Redirect(w, r, authCodeURL, http.StatusSeeOther) } func (a *app) handleCallback(w http.ResponseWriter, r *http.Request) { @@ -195,7 +232,7 @@ func (a *app) handleCallback(w http.ResponseWriter, r *http.Request) { } token, err = oauth2Config.TokenSource(a.ctx, t).Token() default: - http.Error(w, "no code in request", http.StatusBadRequest) + http.Error(w, fmt.Sprintf("no code in request: %q", r.Form), http.StatusBadRequest) return }