diff --git a/oidc/oidc.go b/oidc/oidc.go index bf31a52..8377c89 100644 --- a/oidc/oidc.go +++ b/oidc/oidc.go @@ -97,7 +97,9 @@ func doRequest(ctx context.Context, req *http.Request) (*http.Response, error) { // Provider represents an OpenID Connect server's configuration. type Provider struct { - issuer string + issuer string + alternativeIssuer []string + authURL string tokenURL string deviceAuthURL string @@ -280,15 +282,16 @@ func NewProvider(ctx context.Context, issuer string, alternativeIssuer ...string } } return &Provider{ - issuer: issuerURL, - authURL: p.AuthURL, - tokenURL: p.TokenURL, - deviceAuthURL: p.DeviceAuthURL, - userInfoURL: p.UserInfoURL, - jwksURL: p.JWKSURL, - algorithms: algs, - rawClaims: body, - client: getClient(ctx), + issuer: issuerURL, + alternativeIssuer: alternativeIssuer, + authURL: p.AuthURL, + tokenURL: p.TokenURL, + deviceAuthURL: p.DeviceAuthURL, + userInfoURL: p.UserInfoURL, + jwksURL: p.JWKSURL, + algorithms: algs, + rawClaims: body, + client: getClient(ctx), }, nil } diff --git a/oidc/verify.go b/oidc/verify.go index 52b27b7..3ae4f0f 100644 --- a/oidc/verify.go +++ b/oidc/verify.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "net/http" + "slices" "strings" "time" @@ -50,9 +51,10 @@ type KeySet interface { // IDTokenVerifier provides verification for ID Tokens. type IDTokenVerifier struct { - keySet KeySet - config *Config - issuer string + keySet KeySet + config *Config + issuer string + alternativeIssuer []string } // NewVerifier returns a verifier manually constructed from a key set and issuer URL. @@ -71,8 +73,8 @@ type IDTokenVerifier struct { // // keySet := &oidc.StaticKeySet{PublicKeys: []crypto.PublicKey{pub1, pub2}} // verifier := oidc.NewVerifier("https://accounts.google.com", keySet, config) -func NewVerifier(issuerURL string, keySet KeySet, config *Config) *IDTokenVerifier { - return &IDTokenVerifier{keySet: keySet, config: config, issuer: issuerURL} +func NewVerifier(issuerURL string, keySet KeySet, config *Config, alternativeIssuer ...string) *IDTokenVerifier { + return &IDTokenVerifier{keySet: keySet, config: config, issuer: issuerURL, alternativeIssuer: alternativeIssuer} } // Config is the configuration for an IDTokenVerifier. @@ -142,7 +144,7 @@ func (p *Provider) newVerifier(keySet KeySet, config *Config) *IDTokenVerifier { cp.SupportedSigningAlgs = p.algorithms config = cp } - return NewVerifier(p.issuer, keySet, config) + return NewVerifier(p.issuer, keySet, config, p.alternativeIssuer...) } func parseJWT(p string) ([]byte, error) { @@ -257,14 +259,16 @@ func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDTok } // Check issuer. - if !v.config.SkipIssuerCheck && t.Issuer != v.issuer { + v.alternativeIssuer = append(v.alternativeIssuer, v.issuer) + + if !v.config.SkipIssuerCheck && !slices.Contains(v.alternativeIssuer, t.Issuer) { // Google sometimes returns "accounts.google.com" as the issuer claim instead of // the required "https://accounts.google.com". Detect this case and allow it only // for Google. // // We will not add hooks to let other providers go off spec like this. if !(v.issuer == issuerGoogleAccounts && t.Issuer == issuerGoogleAccountsNoScheme) { - return nil, fmt.Errorf("oidc: id token issued by a different provider, expected %q got %q", v.issuer, t.Issuer) + return nil, fmt.Errorf("oidc: id token issued by a different provider, expected one of %v got %q", v.alternativeIssuer, t.Issuer) } } diff --git a/oidc/verify_test.go b/oidc/verify_test.go index f2e2433..ae3c3a4 100644 --- a/oidc/verify_test.go +++ b/oidc/verify_test.go @@ -46,6 +46,18 @@ func TestVerify(t *testing.T) { signKey: newRSAKey(t), wantErr: true, }, + { + name: "alternative issuer", + issuer: "https://bar", + alternativeIssuer: []string{"https://bar", "https://baz"}, + idToken: `{"iss":"https://foo"}`, + config: Config{ + SkipClientIDCheck: true, + SkipExpiryCheck: true, + }, + signKey: newRSAKey(t), + wantErr: true, + }, { name: "skip issuer check", issuer: "https://bar", @@ -561,6 +573,8 @@ type verificationTest struct { // If not provided defaults to "https://foo" issuer string + alternativeIssuer []string + // JWT payload (just the claims). idToken string @@ -598,7 +612,7 @@ func (v verificationTest) runGetToken(t *testing.T) (*IDToken, error) { } else if v.signKey != nil { ks = &StaticKeySet{PublicKeys: []crypto.PublicKey{v.signKey.pub}} } - verifier := NewVerifier(issuer, ks, &v.config) + verifier := NewVerifier(issuer, ks, &v.config, v.alternativeIssuer...) return verifier.Verify(ctx, token) }