diff --git a/backend/internal/service/jwt_service.go b/backend/internal/service/jwt_service.go index a993e9e4..aa83254c 100644 --- a/backend/internal/service/jwt_service.go +++ b/backend/internal/service/jwt_service.go @@ -7,6 +7,7 @@ import ( "fmt" "time" + "github.com/google/uuid" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" @@ -193,6 +194,7 @@ func (s *JwtService) GenerateAccessToken(user model.User) (string, error) { Expiration(now.Add(s.appConfigService.GetDbConfig().SessionDuration.AsDurationMinutes())). IssuedAt(now). Issuer(s.envConfig.AppURL). + JwtID(uuid.New().String()). Build() if err != nil { return "", fmt.Errorf("failed to build token: %w", err) @@ -247,6 +249,7 @@ func (s *JwtService) BuildIDToken(userClaims map[string]any, clientID string, no Expiration(now.Add(1 * time.Hour)). IssuedAt(now). Issuer(s.envConfig.AppURL). + JwtID(uuid.New().String()). Build() if err != nil { return nil, fmt.Errorf("failed to build token: %w", err) @@ -336,6 +339,7 @@ func (s *JwtService) BuildOAuthAccessToken(user model.User, clientID string) (jw Expiration(now.Add(1 * time.Hour)). IssuedAt(now). Issuer(s.envConfig.AppURL). + JwtID(uuid.New().String()). Build() if err != nil { return nil, fmt.Errorf("failed to build token: %w", err) diff --git a/backend/internal/service/jwt_service_test.go b/backend/internal/service/jwt_service_test.go index e25dc966..5b08f1c7 100644 --- a/backend/internal/service/jwt_service_test.go +++ b/backend/internal/service/jwt_service_test.go @@ -27,6 +27,8 @@ import ( const testEncryptionKey = "0123456789abcdef0123456789abcdef" +const uuidRegexPattern = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + func newTestEnvConfig() *common.EnvConfigSchema { return &common.EnvConfigSchema{ AppURL: "https://test.example.com", @@ -323,6 +325,9 @@ func TestGenerateVerifyAccessToken(t *testing.T) { audience, ok := claims.Audience() _ = assert.True(t, ok, "Audience not found in token") && assert.Equal(t, []string{service.envConfig.AppURL}, audience, "Audience should contain the app URL") + jwtID, ok := claims.JwtID() + _ = assert.True(t, ok, "JWT ID not found in token") && + assert.Regexp(t, uuidRegexPattern, jwtID, "JWT ID is not a UUID") expectedExp := time.Now().Add(1 * time.Hour) expiration, ok := claims.Expiration() @@ -520,6 +525,9 @@ func TestGenerateVerifyIdToken(t *testing.T) { issuer, ok := claims.Issuer() _ = assert.True(t, ok, "Issuer not found in token") && assert.Equal(t, service.envConfig.AppURL, issuer, "Issuer should match app URL") + jwtID, ok := claims.JwtID() + _ = assert.True(t, ok, "JWT ID not found in token") && + assert.Regexp(t, uuidRegexPattern, jwtID, "JWT ID is not a UUID") expectedExp := time.Now().Add(1 * time.Hour) expiration, ok := claims.Expiration() @@ -754,6 +762,9 @@ func TestGenerateVerifyOAuthAccessToken(t *testing.T) { issuer, ok := claims.Issuer() _ = assert.True(t, ok, "Issuer not found in token") && assert.Equal(t, service.envConfig.AppURL, issuer, "Issuer should match app URL") + jwtID, ok := claims.JwtID() + _ = assert.True(t, ok, "JWT ID not found in token") && + assert.Regexp(t, uuidRegexPattern, jwtID, "JWT ID is not a UUID") expectedExp := time.Now().Add(1 * time.Hour) expiration, ok := claims.Expiration()