feat: add a jti per default (#4695)

Signed-off-by: Roger Meier <r.meier@siemens.com>
This commit is contained in:
Roger Meier
2026-03-28 23:07:11 +00:00
committed by GitHub
parent 1558aacc8c
commit 31cf652930
2 changed files with 81 additions and 0 deletions
+3
View File
@@ -21,6 +21,7 @@ import (
"github.com/coreos/go-oidc/v3/oidc"
"github.com/go-jose/go-jose/v4"
"github.com/google/uuid"
"github.com/dexidp/dex/connector"
"github.com/dexidp/dex/server/internal"
@@ -271,6 +272,7 @@ type idTokenClaims struct {
Audience audience `json:"aud"`
Expiry int64 `json:"exp"`
IssuedAt int64 `json:"iat"`
JWTID string `json:"jti,omitempty"`
AuthorizingParty string `json:"azp,omitempty"`
Nonce string `json:"nonce,omitempty"`
AuthTime int64 `json:"auth_time,omitempty"`
@@ -357,6 +359,7 @@ func (s *Server) newIDToken(ctx context.Context, clientID string, claims storage
Nonce: nonce,
Expiry: expiry.Unix(),
IssuedAt: issuedAt.Unix(),
JWTID: uuid.New().String(),
}
// Include auth_time when sessions are enabled and the value is available.
+78
View File
@@ -1099,6 +1099,84 @@ func TestNewIDTokenUsesStoredAlgorithmUntilNextRotation(t *testing.T) {
require.Equal(t, wantCodeHash, claims.CodeHash)
}
func TestNewIDTokenContainsJTI(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger := slog.New(slog.DiscardHandler)
store := memory.New(logger)
now := time.Now().UTC()
err := store.UpdateKeys(ctx, func(keys storage.Keys) (storage.Keys, error) {
keys.SigningKey = &jose.JSONWebKey{
Key: testKey,
KeyID: "test-rs256",
Algorithm: string(jose.RS256),
Use: "sig",
}
keys.SigningKeyPub = &jose.JSONWebKey{
Key: testKey.Public(),
KeyID: "test-rs256",
Algorithm: string(jose.RS256),
Use: "sig",
}
keys.NextRotation = now.Add(time.Hour)
return keys, nil
})
require.NoError(t, err)
localConfig := signer.LocalConfig{
KeysRotationPeriod: time.Hour.String(),
Algorithm: jose.RS256,
}
sig, err := localConfig.Open(ctx, store, time.Hour, func() time.Time { return now }, logger)
require.NoError(t, err)
sig.Start(ctx)
issuerURL, err := url.Parse("https://issuer.example.com")
require.NoError(t, err)
s := &Server{
signer: sig,
issuerURL: *issuerURL,
logger: logger,
now: func() time.Time { return now },
idTokensValidFor: time.Hour,
}
keys, err := sig.ValidationKeys(ctx)
require.NoError(t, err)
require.NotEmpty(t, keys)
extractJTI := func(t *testing.T, idToken string) string {
t.Helper()
jws, err := jose.ParseSigned(idToken, []jose.SignatureAlgorithm{jose.RS256})
require.NoError(t, err)
payload, err := jws.Verify(keys[0])
require.NoError(t, err)
var claims struct {
JTI string `json:"jti"`
}
err = json.Unmarshal(payload, &claims)
require.NoError(t, err)
return claims.JTI
}
token1, _, err := s.newIDToken(ctx, "client", storage.Claims{UserID: "1", Username: "alice"}, []string{"openid"}, "n1", "", "", "mock", time.Time{})
require.NoError(t, err)
token2, _, err := s.newIDToken(ctx, "client", storage.Claims{UserID: "1", Username: "alice"}, []string{"openid"}, "n2", "", "", "mock", time.Time{})
require.NoError(t, err)
jti1 := extractJTI(t, token1)
jti2 := extractJTI(t, token2)
assert.NotEmpty(t, jti1, "jti claim must be present and non-empty")
assert.NotEmpty(t, jti2, "jti claim must be present and non-empty")
assert.NotEqual(t, jti1, jti2, "each token must have a unique jti")
}
func TestSessionMatchesHint(t *testing.T) {
// genSubject("foo", "bar") == "CgNmb28SA2Jhcg" (from TestGetSubject)
assert.True(t, sessionMatchesHint(&storage.AuthSession{UserID: "foo", ConnectorID: "bar"}, "CgNmb28SA2Jhcg"))