Skip to content

Commit 83285e7

Browse files
authored
fix(core): infer JWT algorithms for JWKS keys without alg (#3434)
## Summary - Fix JWT verification for IdPs whose JWKS keys omit `alg`, such as Microsoft Entra. - Pass `jws.WithInferAlgorithmFromKey(true)` to `jwt.WithKeySet` while preserving issuer, audience, skew, kid, and signature validation. - Add regression coverage for a JWKS key with `kid` but no `alg`. ## Jira - DSPX-3172 ## Testing - `~/go/bin/gofumpt -w service/internal/auth/token_verifier.go service/internal/auth/token_verifier_test.go` - `cd service && go test ./internal/auth -run 'TestTokenVerifier|TestNewTokenVerifier' -v` - `cd service && go test ./internal/auth` - `cd service && golangci-lint run --new ./internal/auth` ## Known Existing Failures - `cd service && golangci-lint run ./internal/auth` fails on pre-existing unused `//nolint` directives in `authn.go`. - `make test` fails in `lib/fixtures` on `TestTokenManager_InitialLogin` and `TestTokenManager_CustomTokenBuffer`, unrelated to this auth verifier change. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * JWT verification now infers the signing algorithm from the verification key, allowing token authentication to succeed when JWKS keys omit an explicit algorithm, improving robustness and interoperability. * **Tests** * Added coverage for tokens validated against JWKS entries that do not include an alg field. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: strantalis <strantalis@virtru.com>
1 parent aa23179 commit 83285e7

2 files changed

Lines changed: 42 additions & 2 deletions

File tree

service/internal/auth/token_verifier.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/lestrrat-go/jwx/v2/jwk"
10+
"github.com/lestrrat-go/jwx/v2/jws"
1011
"github.com/lestrrat-go/jwx/v2/jwt"
1112

1213
"github.com/opentdf/platform/service/logger"
@@ -89,7 +90,7 @@ func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, tokenRaw string)
8990
}
9091

9192
token, err := jwt.Parse([]byte(tokenRaw),
92-
jwt.WithKeySet(v.cachedKeySet),
93+
jwt.WithKeySet(v.cachedKeySet, jws.WithInferAlgorithmFromKey(true)),
9394
jwt.WithValidate(true),
9495
jwt.WithIssuer(v.oidcConfiguration.Issuer),
9596
jwt.WithAudience(v.oidcConfiguration.Audience),

service/internal/auth/token_verifier_test.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ type tokenVerifierFixture struct {
2525
}
2626

2727
func newTokenVerifierFixture(t *testing.T) *tokenVerifierFixture {
28+
privateKey, publicKeyJWK := newTokenVerifierKeyPair(t)
29+
require.NoError(t, publicKeyJWK.Set(jwk.AlgorithmKey, jwa.RS256))
30+
31+
return newTokenVerifierFixtureWithPublicKey(t, privateKey, publicKeyJWK)
32+
}
33+
34+
func newTokenVerifierFixtureWithoutPublicKeyAlgorithm(t *testing.T) *tokenVerifierFixture {
35+
privateKey, publicKeyJWK := newTokenVerifierKeyPair(t)
36+
require.NoError(t, publicKeyJWK.Remove(jwk.AlgorithmKey))
37+
_, hasAlgorithm := publicKeyJWK.Get(jwk.AlgorithmKey)
38+
require.False(t, hasAlgorithm)
39+
40+
return newTokenVerifierFixtureWithPublicKey(t, privateKey, publicKeyJWK)
41+
}
42+
43+
func newTokenVerifierKeyPair(t *testing.T) (*rsa.PrivateKey, jwk.Key) {
2844
t.Helper()
2945

3046
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
@@ -33,7 +49,12 @@ func newTokenVerifierFixture(t *testing.T) *tokenVerifierFixture {
3349
publicKeyJWK, err := jwk.FromRaw(privateKey.PublicKey)
3450
require.NoError(t, err)
3551
require.NoError(t, publicKeyJWK.Set(jws.KeyIDKey, "test-key"))
36-
require.NoError(t, publicKeyJWK.Set(jwk.AlgorithmKey, jwa.RS256))
52+
53+
return privateKey, publicKeyJWK
54+
}
55+
56+
func newTokenVerifierFixtureWithPublicKey(t *testing.T, privateKey *rsa.PrivateKey, publicKeyJWK jwk.Key) *tokenVerifierFixture {
57+
t.Helper()
3758

3859
keySet := jwk.NewSet()
3960
require.NoError(t, keySet.AddKey(publicKeyJWK))
@@ -149,6 +170,24 @@ func TestTokenVerifier_VerifyAccessToken(t *testing.T) {
149170
_, err = verifier.VerifyAccessToken(t.Context(), token)
150171
require.Error(t, err)
151172
})
173+
174+
t.Run("valid token with JWKS key missing alg", func(t *testing.T) {
175+
missingAlgFixture := newTokenVerifierFixtureWithoutPublicKeyAlgorithm(t)
176+
177+
missingAlgVerifier, err := NewTokenVerifier(t.Context(), AuthNConfig{
178+
Issuer: missingAlgFixture.server.URL,
179+
Audience: "test-audience",
180+
CacheRefresh: "15m",
181+
TokenSkew: time.Minute,
182+
}, logger.CreateTestLogger())
183+
require.NoError(t, err)
184+
185+
token := missingAlgFixture.signToken(t, missingAlgFixture.server.URL, "test-audience", missingAlgFixture.privateKey)
186+
187+
verifiedToken, err := missingAlgVerifier.VerifyAccessToken(t.Context(), token)
188+
require.NoError(t, err)
189+
assert.Equal(t, "user-123", verifiedToken.Subject())
190+
})
152191
}
153192

154193
func TestTokenVerifier_NilHandling(t *testing.T) {

0 commit comments

Comments
 (0)