package rp import ( "context" "time" jose "github.com/go-jose/go-jose/v4" "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" ) // VerifyTokens implement the Token Response Validation as defined in OIDC specification // https://openid.net/specs/openid-connect-core-1_0.html#TokenResponseValidation func VerifyTokens[C oidc.IDClaims](ctx context.Context, accessToken, idToken string, v *IDTokenVerifier) (claims C, err error) { ctx, span := client.Tracer.Start(ctx, "VerifyTokens") defer span.End() var nilClaims C claims, err = VerifyIDToken[C](ctx, idToken, v) if err != nil { return nilClaims, err } if err := VerifyAccessToken(accessToken, claims.GetAccessTokenHash(), claims.GetSignatureAlgorithm()); err != nil { return nilClaims, err } return claims, nil } // VerifyIDToken validates the id token according to // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation func VerifyIDToken[C oidc.Claims](ctx context.Context, token string, v *IDTokenVerifier) (claims C, err error) { ctx, span := client.Tracer.Start(ctx, "VerifyIDToken") defer span.End() var nilClaims C decrypted, err := oidc.DecryptToken(token) if err != nil { return nilClaims, err } payload, err := oidc.ParseToken(decrypted, &claims) if err != nil { return nilClaims, err } if err := oidc.CheckSubject(claims); err != nil { return nilClaims, err } if err = oidc.CheckIssuer(claims, v.Issuer); err != nil { return nilClaims, err } if err = oidc.CheckAudience(claims, v.ClientID); err != nil { return nilClaims, err } if err = oidc.CheckAuthorizedParty(claims, v.ClientID); err != nil { return nilClaims, err } if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs, v.KeySet); err != nil { return nilClaims, err } if err = oidc.CheckExpiration(claims, v.Offset); err != nil { return nilClaims, err } if err = oidc.CheckIssuedAt(claims, v.MaxAgeIAT, v.Offset); err != nil { return nilClaims, err } if v.Nonce != nil { if err = oidc.CheckNonce(claims, v.Nonce(ctx)); err != nil { return nilClaims, err } } if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR); err != nil { return nilClaims, err } if err = oidc.CheckAuthTime(claims, v.MaxAge); err != nil { return nilClaims, err } return claims, nil } type IDTokenVerifier oidc.Verifier // VerifyAccessToken validates the access token according to // https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowTokenValidation func VerifyAccessToken(accessToken, atHash string, sigAlgorithm jose.SignatureAlgorithm) error { if atHash == "" { return nil } actual, err := oidc.ClaimHash(accessToken, sigAlgorithm) if err != nil { return err } if actual != atHash { return oidc.ErrAtHash } return nil } // NewIDTokenVerifier returns a oidc.Verifier suitable for ID token verification. func NewIDTokenVerifier(issuer, clientID string, keySet oidc.KeySet, options ...VerifierOption) *IDTokenVerifier { v := &IDTokenVerifier{ Issuer: issuer, ClientID: clientID, KeySet: keySet, Offset: time.Second, Nonce: func(_ context.Context) string { return "" }, } for _, opts := range options { opts(v) } return v } // VerifierOption is the type for providing dynamic options to the IDTokenVerifier type VerifierOption func(*IDTokenVerifier) // WithIssuedAtOffset mitigates the risk of iat to be in the future // because of clock skews with the ability to add an offset to the current time func WithIssuedAtOffset(offset time.Duration) VerifierOption { return func(v *IDTokenVerifier) { v.Offset = offset } } // WithIssuedAtMaxAge provides the ability to define the maximum duration between iat and now func WithIssuedAtMaxAge(maxAge time.Duration) VerifierOption { return func(v *IDTokenVerifier) { v.MaxAgeIAT = maxAge } } // WithNonce sets the function to check the nonce func WithNonce(nonce func(context.Context) string) VerifierOption { return func(v *IDTokenVerifier) { v.Nonce = nonce } } // WithACRVerifier sets the verifier for the acr claim func WithACRVerifier(verifier oidc.ACRVerifier) VerifierOption { return func(v *IDTokenVerifier) { v.ACR = verifier } } // WithAuthTimeMaxAge provides the ability to define the maximum duration between auth_time and now func WithAuthTimeMaxAge(maxAge time.Duration) VerifierOption { return func(v *IDTokenVerifier) { v.MaxAge = maxAge } } // WithSupportedSigningAlgorithms overwrites the default RS256 signing algorithm func WithSupportedSigningAlgorithms(algs ...string) VerifierOption { return func(v *IDTokenVerifier) { v.SupportedSignAlgs = algs } }