Skip to content

Commit 6f39617

Browse files
authored
Merge pull request #78 from grafbase/gb-9034-multiple-audience
feat(jwt): Add support for multiple audiences in the configuration
2 parents cb157af + b9263ab commit 6f39617

File tree

9 files changed

+200
-28
lines changed

9 files changed

+200
-28
lines changed

Cargo.lock

Lines changed: 10 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

devenv.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
inputs,
66
...
77
}: {
8-
packages = [pkgs.git pkgs.rustup pkgs.openssl pkgs.cargo-nextest pkgs.taplo pkgs.protobuf];
8+
packages = [pkgs.git pkgs.rustup pkgs.openssl pkgs.cargo-nextest pkgs.taplo pkgs.cargo-insta pkgs.protobuf];
99
}

extensions/jwt/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## [1.1.0] - 2025-05-01
4+
5+
- Support for an array in `audience` configuration.
6+
- Better configuration de-serialization errors.
7+
38
## [1.0.0] - 2025-05-01
49

510
- No changes.

extensions/jwt/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ url = "https://example.com/.well-known/jwks.json"
3232
# issuer = "example.com"
3333

3434
# Expected `aud` claim. By default it is NOT validated.
35+
# If a list is provided, only one of those audience must match.
36+
# Note that `aud` claim can be an array in all cases in the JWT and only one of the `aud` claims
37+
# must match an audience defined here.
3538
# audience = "my-project"
39+
# audience = ["my-project", "my-other-name"]
3640

3741
# How long the JWKS will be cached, in seconds.
3842
poll_interval = 60

extensions/jwt/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[extension]
22
name = "jwt"
3-
version = "1.0.0"
3+
version = "1.1.0"
44
kind = "authentication"
55
description = "Restrict access to your graph with JWT"
66
repository_url = "https://github.com/grafbase/extensions/tree/main/extensions/jwt"

extensions/jwt/src/config.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub(crate) struct Config {
77
pub url: Url,
88
pub poll_interval: Duration,
99
pub issuer: Option<String>,
10-
pub audience: Option<String>,
10+
pub audience: Option<Vec<String>>,
1111
pub locations: Vec<Location>,
1212
}
1313

@@ -57,12 +57,14 @@ impl<'de> serde::Deserialize<'de> for Config {
5757
}
5858
}
5959

60+
#[serde_with::serde_as]
6061
#[derive(Debug, serde::Deserialize)]
6162
#[serde(deny_unknown_fields)]
6263
struct TomlConfig {
6364
url: Url,
6465
issuer: Option<String>,
65-
audience: Option<String>,
66+
#[serde_as(deserialize_as = "Option<serde_with::OneOrMany<_>>")]
67+
audience: Option<Vec<String>>,
6668
#[serde(default = "default_poll_interval", deserialize_with = "deserialize_duration")]
6769
poll_interval: Duration,
6870
header_name: Option<String>,

extensions/jwt/src/decoder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ impl<'a> Decoder<'a> {
3838
}
3939

4040
if let Some(expected) = self.config.audience.as_ref() {
41-
let audience = token.claims().custom.audience.as_ref()?;
42-
if audience.iter().all(|aud| aud != expected) {
41+
let aud_claims = token.claims().custom.audience.as_ref()?;
42+
if aud_claims.iter().all(|claim| !expected.contains(claim)) {
4343
return None;
4444
}
4545
}

extensions/jwt/tests/hydra.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub const ISSUER: &str = "http://127.0.0.1:4444";
99
pub const JWKS_URI: &str = "http://127.0.0.1:4444/.well-known/jwks.json";
1010
pub const AUDIENCE: &str = "integration-tests";
1111
pub const OTHER_AUDIENCE: &str = "other-audience";
12+
pub const THIRD_AUDIENCE: &str = "third-audience";
1213
const HYDRA_ADMIN_URL: &str = "http://127.0.0.1:4445";
1314
// Second provider
1415
pub const ISSUER_2: &str = "http://127.0.0.1:4454";
@@ -53,7 +54,7 @@ impl OryHydraOpenIDProvider {
5354
access_token_strategy: Some("jwt".into()),
5455
grant_types: Some(vec!["client_credentials".into()]),
5556
// Allowed audiences
56-
audience: Some(vec![AUDIENCE.into(), OTHER_AUDIENCE.into()]),
57+
audience: Some(vec![AUDIENCE.into(), OTHER_AUDIENCE.into(), THIRD_AUDIENCE.into()]),
5758
// Allowed scopes
5859
scope: Some(format!("{READ_SCOPE} {WRITE_SCOPE}")),
5960
..ory_client::models::OAuth2Client::new()

0 commit comments

Comments
 (0)