Merge commit from fork

* feat(jwt idp): manage and validate audience

* translations

* fix tests

* address comments

* update migration version

* fix merge
This commit is contained in:
Livio Spring
2026-06-15 15:27:47 +02:00
committed by GitHub
parent fad02c6d9f
commit d184e976fc
57 changed files with 379 additions and 55 deletions
+3 -1
View File
@@ -28,6 +28,7 @@ type Provider struct {
issuer string
jwtEndpoint string
keysEndpoint string
audience string
isLinkingAllowed bool
isCreationAllowed bool
isAutoCreation bool
@@ -67,13 +68,14 @@ func WithAutoUpdate() ProviderOpts {
}
// New creates a JWT provider
func New(name, issuer, jwtEndpoint, keysEndpoint, headerName string, encryptionAlg crypto.EncryptionAlgorithm, options ...ProviderOpts) (*Provider, error) {
func New(name, issuer, jwtEndpoint, keysEndpoint, headerName, audience string, encryptionAlg crypto.EncryptionAlgorithm, options ...ProviderOpts) (*Provider, error) {
provider := &Provider{
name: name,
issuer: issuer,
jwtEndpoint: jwtEndpoint,
keysEndpoint: keysEndpoint,
headerName: headerName,
audience: audience,
encryptionAlg: encryptionAlg,
}
for _, option := range options {
+9
View File
@@ -20,6 +20,7 @@ func TestProvider_BeginAuth(t *testing.T) {
jwtEndpoint string
keysEndpoint string
headerName string
audience string
encryptionAlg func(t *testing.T) crypto.EncryptionAlgorithm
}
type args struct {
@@ -43,6 +44,7 @@ func TestProvider_BeginAuth(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
audience: "audience",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -64,6 +66,7 @@ func TestProvider_BeginAuth(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
audience: "audience",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -83,6 +86,7 @@ func TestProvider_BeginAuth(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
audience: "audience",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -108,6 +112,7 @@ func TestProvider_BeginAuth(t *testing.T) {
tt.fields.jwtEndpoint,
tt.fields.keysEndpoint,
tt.fields.headerName,
tt.fields.audience,
tt.fields.encryptionAlg(t),
)
require.NoError(t, err)
@@ -135,6 +140,7 @@ func TestProvider_Options(t *testing.T) {
jwtEndpoint string
keysEndpoint string
headerName string
audience string
encryptionAlg func(t *testing.T) crypto.EncryptionAlgorithm
opts []ProviderOpts
}
@@ -159,6 +165,7 @@ func TestProvider_Options(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
audience: "audience",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -181,6 +188,7 @@ func TestProvider_Options(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
audience: "audience",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -211,6 +219,7 @@ func TestProvider_Options(t *testing.T) {
tt.fields.jwtEndpoint,
tt.fields.keysEndpoint,
tt.fields.headerName,
tt.fields.audience,
tt.fields.encryptionAlg(t),
tt.fields.opts...,
)
+5
View File
@@ -100,6 +100,11 @@ func (s *Session) validateToken(ctx context.Context, token string) (*oidc.IDToke
return nil, fmt.Errorf("%w: %v", ErrInvalidToken, err)
}
if s.Provider.audience != "" {
if err = oidc.CheckAudience(claims, s.Provider.audience); err != nil {
return nil, fmt.Errorf("%w: %v", ErrInvalidToken, err)
}
}
return claims, nil
}
@@ -27,6 +27,7 @@ func TestSession_FetchUser(t *testing.T) {
jwtEndpoint string
keysEndpoint string
headerName string
audience string
encryptionAlg func(t *testing.T) crypto.EncryptionAlgorithm
httpMock func(issuer string)
authURL string
@@ -60,6 +61,7 @@ func TestSession_FetchUser(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
audience: "",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -83,6 +85,36 @@ func TestSession_FetchUser(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
audience: "",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
httpMock: func(issuer string) {
gock.New(issuer).
Get("/keys").
Reply(200).
JSON(keys(t))
},
authURL: "https://auth.com/jwt?authRequestID=testState",
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{},
IDToken: "invalidToken",
},
},
want: want{
err: func(err error) bool {
return errors.Is(err, ErrInvalidToken)
},
},
},
{
name: "invalid token (audience)",
fields: fields{
issuer: "https://jwt.com",
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
audience: "audience",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -214,6 +246,69 @@ func TestSession_FetchUser(t *testing.T) {
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
audience: "clientID",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
httpMock: func(issuer string) {
gock.New(issuer).
Get("/keys").
Reply(200).
JSON(keys(t))
},
authURL: "https://auth.com/jwt?authRequestID=testState",
tokens: &oidc.Tokens[*oidc.IDTokenClaims]{
Token: &oauth2.Token{},
IDToken: idToken(t, "https://jwt.com", time.Now().Add(time.Hour), 0),
IDTokenClaims: &oidc.IDTokenClaims{
TokenClaims: oidc.TokenClaims{
Subject: "sub",
},
UserInfoProfile: oidc.UserInfoProfile{
Picture: "picture",
Name: "firstname lastname",
GivenName: "firstname",
FamilyName: "lastname",
Nickname: "nickname",
PreferredUsername: "username",
Profile: "profile",
Locale: oidc.NewLocale(language.English),
},
UserInfoEmail: oidc.UserInfoEmail{
Email: "email",
EmailVerified: oidc.Bool(true),
},
UserInfoPhone: oidc.UserInfoPhone{
PhoneNumber: "phone",
PhoneNumberVerified: true,
},
},
},
},
want: want{
id: "sub",
firstName: "firstname",
lastName: "lastname",
displayName: "firstname lastname",
nickName: "nickname",
preferredUsername: "username",
email: "email",
isEmailVerified: true,
phone: "phone",
isPhoneVerified: true,
preferredLanguage: language.English,
avatarURL: "picture",
profile: "profile",
},
},
{
name: "successful fetch (no audience check)",
fields: fields{
issuer: "https://jwt.com",
jwtEndpoint: "https://auth.com/jwt",
keysEndpoint: "https://jwt.com/keys",
headerName: "jwt-header",
audience: "",
encryptionAlg: func(t *testing.T) crypto.EncryptionAlgorithm {
return crypto.CreateMockEncryptionAlg(gomock.NewController(t))
},
@@ -281,6 +376,7 @@ func TestSession_FetchUser(t *testing.T) {
tt.fields.jwtEndpoint,
tt.fields.keysEndpoint,
tt.fields.headerName,
tt.fields.audience,
tt.fields.encryptionAlg(t),
)
require.NoError(t, err)