From f2567ccdfe107a23a79fbb7eb11eabcbdda0bfec Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Thu, 6 Aug 2026 13:22:10 +0200 Subject: [PATCH] feat(mock): enforce unique jti and lifetime for client assertions --- pkg/mock/openid.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/pkg/mock/openid.go b/pkg/mock/openid.go index 7b08278..c6278a6 100644 --- a/pkg/mock/openid.go +++ b/pkg/mock/openid.go @@ -135,6 +135,7 @@ type ( ) type IdentityProviderHandler struct { + ClientAssertions map[string]bool Codes map[Code]*AuthorizeRequest Config openidconfig.Config Provider *TestProvider @@ -146,6 +147,7 @@ type IdentityProviderHandler struct { func newIdentityProviderHandler(provider *TestProvider, cfg openidconfig.Config) *IdentityProviderHandler { return &IdentityProviderHandler{ + ClientAssertions: make(map[string]bool), Codes: make(map[Code]*AuthorizeRequest), Config: cfg, Provider: provider, @@ -643,8 +645,9 @@ func (ip *IdentityProviderHandler) validateClientAuthentication(w http.ResponseW jwt.WithIssuer(ip.Config.Client().ClientID()), jwt.WithSubject(ip.Config.Client().ClientID()), jwt.WithAudience(ip.Config.Provider().Issuer()), + jwt.WithMaxDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey), } - _, err = jwt.Parse([]byte(clientAssertion), opts...) + clientAssertionJwt, err := jwt.Parse([]byte(clientAssertion), opts...) if err != nil { w.WriteHeader(http.StatusUnauthorized) v := url.Values{} @@ -654,6 +657,25 @@ func (ip *IdentityProviderHandler) validateClientAuthentication(w http.ResponseW return fmt.Errorf("%s: %+v", v.Encode(), err) } + var jti any + err = clientAssertionJwt.Get(jwt.JwtIDKey, &jti) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return fmt.Errorf("client_assertion missing jti claim") + } + + jtiString, ok := jti.(string) + if !ok { + w.WriteHeader(http.StatusBadRequest) + return fmt.Errorf("client_assertion jti claim is not a string") + } + + if ip.ClientAssertions[jtiString] { + w.WriteHeader(http.StatusBadRequest) + return fmt.Errorf("client_assertion with jti %q has already been used", jtiString) + } + ip.ClientAssertions[jtiString] = true + return nil }