diff --git a/service/internal/auth/otjwt/jwt.go b/service/internal/auth/otjwt/jwt.go index 103224a..2b06da5 100644 --- a/service/internal/auth/otjwt/jwt.go +++ b/service/internal/auth/otjwt/jwt.go @@ -33,6 +33,13 @@ func parseJwtToken(cfg *config.Config, jwtString string) (*jwt.Token, error) { return parseJwtTokenWithHMAC(cfg, jwtString) } +func parserOptionsWithAudience(cfg *config.Config) []jwt.ParserOption { + if cfg.AuthJwtAud == "" { + return nil + } + return []jwt.ParserOption{jwt.WithAudience(cfg.AuthJwtAud)} +} + func getClaimsFromJwtToken(cfg *config.Config, jwtString string) (jwt.MapClaims, error) { token, err := parseJwtToken(cfg, jwtString) @@ -56,7 +63,8 @@ func parseJwtTokenWithRemoteKey(cfg *config.Config, jwtToken string) (*jwt.Token return nil, err } - return jwt.Parse(jwtToken, jwksVerifier.Keyfunc, jwt.WithAudience(cfg.AuthJwtAud)) + opts := parserOptionsWithAudience(cfg) + return jwt.Parse(jwtToken, jwksVerifier.Keyfunc, opts...) } var ( @@ -148,24 +156,30 @@ func parseJwtTokenWithLocalKey(cfg *config.Config, jwtString string) (*jwt.Token return nil, err } - return jwt.Parse(jwtString, func(token *jwt.Token) (interface{}, error) { + keyFunc := func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("parseJwt expected token algorithm RSA but got: %v", token.Header["alg"]) } return pubKey, nil - }) + } + + opts := parserOptionsWithAudience(cfg) + return jwt.Parse(jwtString, keyFunc, opts...) } // Hash-based Message Authentication Code func parseJwtTokenWithHMAC(cfg *config.Config, jwtString string) (*jwt.Token, error) { - return jwt.Parse(jwtString, func(token *jwt.Token) (interface{}, error) { + keyFunc := func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("parseJwt expected token algorithm HMAC but got: %v", token.Header["alg"]) } return []byte(cfg.AuthJwtHmacSecret), nil - }) + } + + opts := parserOptionsWithAudience(cfg) + return jwt.Parse(jwtString, keyFunc, opts...) } func lookupClaimValueOrDefault(claims jwt.MapClaims, key string, def string) string { diff --git a/service/internal/auth/otjwt/jwt_test.go b/service/internal/auth/otjwt/jwt_test.go index d3f48a8..ed4e3af 100644 --- a/service/internal/auth/otjwt/jwt_test.go +++ b/service/internal/auth/otjwt/jwt_test.go @@ -66,12 +66,19 @@ func newMux() *http.ServeMux { } func createJWTTokenWithExpiration(t *testing.T, privateKey *rsa.PrivateKey, expire int64) string { + return createJWTTokenWithExpirationAndAudience(t, privateKey, expire, "") +} + +func createJWTTokenWithExpirationAndAudience(t *testing.T, privateKey *rsa.PrivateKey, expire int64, audience string) string { token := jwt.New(jwt.SigningMethodRS256) claims := token.Claims.(jwt.MapClaims) claims["nbf"] = time.Now().Unix() - 1000 claims["exp"] = time.Now().Unix() + expire claims["sub"] = "test" claims["olivetinGroup"] = "test" + if audience != "" { + claims["aud"] = audience + } tokenStr, err := token.SignedString(privateKey) if err != nil { @@ -108,6 +115,10 @@ func verifyJWTResponse(t *testing.T, res *http.Response, expectCode int) { } func testJwkValidation(t *testing.T, expire int64, expectCode int) { + testJwkValidationWithAudience(t, expire, expectCode, "", "") +} + +func testJwkValidationWithAudience(t *testing.T, expire int64, expectCode int, configAudience, tokenAudience string) { privateKey, publicKeyPath := createKeys(t) defer os.Remove(publicKeyPath) @@ -116,8 +127,9 @@ func testJwkValidation(t *testing.T, expire int64, expectCode int) { cfg.AuthJwtClaimUsername = "sub" cfg.AuthJwtClaimUserGroup = "olivetinGroup" cfg.AuthJwtHeader = "Authorization" + cfg.AuthJwtAud = configAudience - tokenStr := createJWTTokenWithExpiration(t, privateKey, expire) + tokenStr := createJWTTokenWithExpirationAndAudience(t, privateKey, expire, tokenAudience) handler := setupJWTTestHandler(t, cfg) srv := httptest.NewServer(handler) @@ -135,6 +147,14 @@ func TestJWTSignatureVerificationFails(t *testing.T) { testJwkValidation(t, -500, 403) } +func TestJWTAudienceValidationRejectsWrongAudience(t *testing.T) { + testJwkValidationWithAudience(t, 1000, 403, "expected-audience", "wrong-audience") +} + +func TestJWTAudienceValidationAcceptsCorrectAudience(t *testing.T) { + testJwkValidationWithAudience(t, 1000, 200, "expected-audience", "expected-audience") +} + func createJWTTokenWithGroups(t *testing.T, privateKey *rsa.PrivateKey, groups interface{}) string { token := jwt.New(jwt.SigningMethodRS256) claims := token.Claims.(jwt.MapClaims)