diff --git a/internal/node/id_generator.go b/internal/node/id_generator.go index 76b070c4..bbd50c4f 100644 --- a/internal/node/id_generator.go +++ b/internal/node/id_generator.go @@ -23,6 +23,7 @@ const ( DuoScheme = "DUO-" OryScheme = "ORY-" IdpScheme = "IDP-" + PingScheme = "PING-" SelfScheme = "AGNTCY-" ) @@ -65,6 +66,8 @@ func (g *idGenerator) GenerateFromProof( scheme = OryScheme case oidc.IdpProviderName: scheme = IdpScheme + case oidc.PingProviderName: + scheme = PingScheme case oidc.SelfProviderName: scheme = SelfScheme default: diff --git a/pkg/oidc/parser.go b/pkg/oidc/parser.go index 07e5cc6a..107f2283 100644 --- a/pkg/oidc/parser.go +++ b/pkg/oidc/parser.go @@ -38,6 +38,7 @@ const ( DuoProviderName OryProviderName IdpProviderName + PingProviderName SelfProviderName ) @@ -55,9 +56,11 @@ type providerMetadata struct { JWKSURL string `json:"jwks_uri"` } -const defaultCacheSize = 10 * 1024 * 1024 // 10MB -const defaultCacheExpiration = 24 // 24 hours -const defaultAcceptableSkew = 5 * time.Second // 5 seconds +const ( + defaultCacheSize = 10 * 1024 * 1024 // 10MB + defaultCacheExpiration = 24 // 24 hours + defaultAcceptableSkew = 5 * time.Second // 5 seconds +) type CachedJwks struct { Jwks string @@ -122,7 +125,9 @@ func (p *parser) VerifyJwt(ctx context.Context, parsedJwt *ParsedJWT) error { } // Verify the JWT signature - _, err = jws.Verify([]byte(*parsedJwt.jwt), jws.WithKeySet(jwks)) + // The algorithm is inferred from the key automatically + // In some providers there is no "alg" specified in the JWKS + _, err = jws.Verify([]byte(*parsedJwt.jwt), jws.WithKeySet(jwks, jws.WithInferAlgorithmFromKey(true))) if err != nil { return err } @@ -205,9 +210,28 @@ func (p *parser) GetClaims( return nil, errors.New("failed to decode JWT: missing 'iss' claim") } - subject, ok := jwtToken.Subject() - if !ok { - return nil, errors.New("failed to decode JWT: missing 'sub' claim") + var subOk bool + + var cidError, clientIDError error + + var sub, cid, clientID string + + // Get the subject from 'sub', 'cid' or 'client_id' claim + sub, subOk = jwtToken.Subject() + cidError = jwtToken.Get("cid", &cid) + clientIDError = jwtToken.Get("client_id", &clientID) + + if !subOk && cidError != nil && clientIDError != nil { + return nil, errors.New("failed to decode JWT: missing 'sub', 'cid' or 'client_id' claim") + } + + subject := sub + if subject == "" { + if cidError == nil && cid != "" { + subject = cid + } else if clientIDError == nil && clientID != "" { + subject = clientID + } } var subJWK map[string]any diff --git a/pkg/oidc/provider.go b/pkg/oidc/provider.go index fe6a93ce..6b7841c5 100644 --- a/pkg/oidc/provider.go +++ b/pkg/oidc/provider.go @@ -33,6 +33,8 @@ func (p *parser) detectProviderName( return OktaProviderName, nil case isDuo(headers, providerUrl.Host): return DuoProviderName, nil + case isPing(headers, providerUrl.Host): + return PingProviderName, nil default: return IdpProviderName, nil } @@ -53,6 +55,13 @@ func isDuo(headers http.Header, host string) bool { strings.HasPrefix(host, "duosecurity.com")) } +func isPing(_ http.Header, host string) bool { + return strings.HasSuffix(host, "pingone.com") || + strings.HasSuffix(host, "pingone.eu") || + strings.HasSuffix(host, "pingone.com.au") || + strings.HasSuffix(host, "pingone.ca") +} + func getProviderMetadata(ctx context.Context, issuer string) (*providerMetadata, error) { metadata, oidcErr := getOidcProviderMetadata(ctx, issuer) if oidcErr == nil {