Repository: openfga/openfga

Issue:
Release 1.9.3 breaks OIDC authentication
### Checklist

- [x] I have looked into the [README](https://github.com/openfga/openfga/blob/main/README.md) and have not found a suitable solution or answer.
- [x] I have looked into the [documentation](https://openfga.dev/docs) and have not found a suitable solution or answer.
- [x] I have searched the [issues](https://github.com/openfga/openfga/issues) and have not found a suitable solution or answer.
- [x] I have upgraded to the [latest version](https://github.com/openfga/openfga/releases/latest) of OpenFGA and the issue still persists.
- [x] I have searched the [OpenFGA Community](https://openfga.dev/community) and have not found a suitable solution or answer.
- [x] I agree to the terms within the [OpenFGA Code of Conduct](https://github.com/openfga/.github/blob/main/CODE_OF_CONDUCT.md).

### Description

This morning our renovate pipeline updated our docker compose files used for local dev to latest OpenFGA `1.9.3`.

Without any other change to the repo, the OIDC authentication fails with an `invalid_claims` response breaking all integration tests working with OpenFGA api.

This is reproducible locally by my team members.

Downgrading to `1.9.2` solves the issue for us.

The response returned to the client is

```json
{
  "code": "invalid_claims",
  "message": "invalid claims"
}
```

We have been lucky to pin down the OpenFGA version to `1.9.2` in our Helm deployments to the cluster.

I could not find anything related in the CHANGELOG or release docs.

### OpenFGA version

1.9.3

### How are you running OpenFGA?

In Docker

### What datastore are you using?

In-Memory

### OpenFGA Flags

no

### Logs

```json
{"level":"info","timestamp":1755069318.6269414,"msg":"grpc_req_complete","build.version":"v1.9.3","build.commit":"29a0aac987d121d37a7bd293a026b9521a8d6b24","grpc_service":"openfga.v1.OpenFGAService","grpc_method":"ListStores","grpc_type":"unary","trace_id":"954a99b924ef6684e0ce8e4806add890","user_agent":"HoppscotchKernel/0.2.0","raw_request":{"page_size":null,"continuation_token":"","name":""},"raw_response":{"code":"invalid_claims","message":"invalid claims"},"query_duration_ms":"0","peer.address":"127.0.0.1:53280","request_id":"954a99b924ef6684e0ce8e4806add890","grpc_code":1004,"error":"rpc error: code = Code(1004) desc = invalid claims"}
```

Discussion:
@rhamzeh: Thanks for reporting @holgerstolzenberg, we'll take a look@holgerstolzenberg: Thanks for the quick resolution. Is this is potentially breaking operations, any chance to have that released soon and mode available in a fixed Helm package?

✌️@adriantam: > Thanks for the quick resolution. Is this is potentially breaking operations, any chance to have that released soon and mode available in a fixed Helm package?
> 
> ✌️

@holgerstolzenberg : we plan to release this in the next day or two (if not today).@rhamzeh: @holgerstolzenberg - released in [v1.9.4](https://github.com/openfga/openfga/releases/tag/v1.9.4), helm chart update being released shortly

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 700988b7..a4cd5a57 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 Try to keep listed changes to a concise bulleted list of simple explanations of changes. Aim for the amount of information needed so that readers can understand where they would look in the codebase to investigate the changes' implementation, or where they would look in the documentation to understand how to make use of the change in practice - better yet, link directly to the docs and provide detailed information there. Only elaborate if doing so is required to avoid breaking changes or experimental features from ruining someone's day.
 
 ## [Unreleased]
+### Changed
+- Fix breaking changed introuced after upgrading the JWT dependency from v5.2.2 to v.5.3.0. [#2636](https://github.com/openfga/openfga/pull/2636)
 
 ## [1.9.3] - 2025-08-11
 ### Added
diff --git a/internal/authn/oidc/oidc.go b/internal/authn/oidc/oidc.go
index ff67ec8d..e38802bf 100644
--- a/internal/authn/oidc/oidc.go
+++ b/internal/authn/oidc/oidc.go
@@ -81,12 +81,17 @@ func (oidc *RemoteOidcAuthenticator) Authenticate(requestContext context.Context
 		return nil, authn.ErrMissingBearerToken
 	}
 
-	jwtParser := jwt.NewParser(
+	options := []jwt.ParserOption{
 		jwt.WithValidMethods([]string{"RS256"}),
 		jwt.WithIssuedAt(),
 		jwt.WithExpirationRequired(),
-		jwt.WithAudience(oidc.Audience),
-	)
+	}
+
+	if strings.TrimSpace(oidc.Audience) != "" {
+		options = append(options, jwt.WithAudience(oidc.Audience))
+	}
+
+	jwtParser := jwt.NewParser(options...)
 
 	token, err := jwtParser.Parse(authHeader, func(token *jwt.Token) (any, error) {
 		return oidc.JWKs.Keyfunc(token)



Test patch:
diff --git a/internal/authn/oidc/oidc_test.go b/internal/authn/oidc/oidc_test.go
index 08a484a7..27cf92cd 100644
--- a/internal/authn/oidc/oidc_test.go
+++ b/internal/authn/oidc/oidc_test.go
@@ -242,6 +242,29 @@ func TestRemoteOidcAuthenticator_Authenticate(t *testing.T) {
 		testDescription string
 		testSetup       func() (*RemoteOidcAuthenticator, context.Context, Config, error)
 	}{
+		{
+			testDescription: "empty_audience",
+			testSetup: func() (*RemoteOidcAuthenticator, context.Context, Config, error) {
+				return quickConfigSetup(Config{
+					jwkKid:         "kid_2",
+					jwtKid:         "kid_2",
+					issuerURL:      "right_issuer",
+					audience:       "",
+					issuerAliases:  nil,
+					subjects:       nil,
+					clientIDClaims: []string{"custom_claim", "custom_claim_2"},
+					jwtClaims: jwt.MapClaims{
+						"iss":            "right_issuer",
+						"aud":            "",
+						"sub":            "some-user",
+						"custom_claim_2": customClientID,
+						"scope":          scopes,
+						"exp":            time.Now().Add(10 * time.Minute).Unix(),
+					},
+					privateKeyOverride: nil,
+				})
+			},
+		},
 		{
 			testDescription: "when_the_token_is_valid,_it_MUST_return_the_token_subject_and_its_associated_scopes",
 			testSetup: func() (*RemoteOidcAuthenticator, context.Context, Config, error) {