From d17cb7e4a5564cb7de2c0e0758cdcf187824b2ce Mon Sep 17 00:00:00 2001 From: James Date: Tue, 5 Aug 2025 16:11:16 -0400 Subject: [PATCH 01/11] audience --- docker-compose.yaml | 3 +++ src/stac_auth_proxy/app.py | 1 + src/stac_auth_proxy/config.py | 1 + src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py | 3 +++ 4 files changed, 8 insertions(+) diff --git a/docker-compose.yaml b/docker-compose.yaml index 867cadb8..551a4991 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -91,6 +91,9 @@ services: UPSTREAM_URL: ${UPSTREAM_URL:-http://stac:8001} OIDC_DISCOVERY_URL: ${OIDC_DISCOVERY_URL:-http://localhost:8888/.well-known/openid-configuration} OIDC_DISCOVERY_INTERNAL_URL: ${OIDC_DISCOVERY_INTERNAL_URL:-http://oidc:8888/.well-known/openid-configuration} + AUDIENCE: ${AUDIENCE} + + env_file: - path: .env required: false diff --git a/src/stac_auth_proxy/app.py b/src/stac_auth_proxy/app.py index 90611a9f..a456ef2a 100644 --- a/src/stac_auth_proxy/app.py +++ b/src/stac_auth_proxy/app.py @@ -157,6 +157,7 @@ async def lifespan(app: FastAPI): private_endpoints=settings.private_endpoints, default_public=settings.default_public, oidc_discovery_url=settings.oidc_discovery_internal_url, + allowed_jwt_audiences=settings.audience, ) if settings.root_path or settings.upstream_url.path != "/": diff --git a/src/stac_auth_proxy/config.py b/src/stac_auth_proxy/config.py index c1d8bedc..5c132a1f 100644 --- a/src/stac_auth_proxy/config.py +++ b/src/stac_auth_proxy/config.py @@ -39,6 +39,7 @@ class Settings(BaseSettings): upstream_url: HttpUrl oidc_discovery_url: HttpUrl oidc_discovery_internal_url: HttpUrl + audience: str root_path: str = "" override_host: bool = True diff --git a/src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py b/src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py index 7d7bc177..c7ebed3e 100644 --- a/src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py +++ b/src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py @@ -173,6 +173,9 @@ def validate_token( detail="Not enough permissions", headers={"WWW-Authenticate": f'Bearer scope="{scope}"'}, ) + + # if required_permissions: + # for perm in required_permissions: return payload @property From 4504c2b12a14e48545ee9d64c5b2000190c7ac2e Mon Sep 17 00:00:00 2001 From: James Date: Tue, 5 Aug 2025 22:34:23 -0400 Subject: [PATCH 02/11] docs --- docs/configuration.md | 8 ++++++++ src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py | 2 -- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 631dcbcc..ac738aaf 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -79,6 +79,14 @@ The application is configurable via environment variables. **Required:** No, defaults to the value of `OIDC_DISCOVERY_URL` **Example:** `http://auth/.well-known/openid-configuration` +### `AUDIENCE` + +: The unique identifier of your API resource server + + The AUDIENCE environment variable specifies the intended recipient of OAuth2 access tokens. This value represents the unique identifier of your API resource server and must match the `aud` (audience) claim present in incoming OAuth2 access tokens. If undefined the API will not impose a check on the `aud` claim + +OAuth2 Audience Claim + ### `DEFAULT_PUBLIC` : Default access policy for endpoints diff --git a/src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py b/src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py index c7ebed3e..8f26b025 100644 --- a/src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py +++ b/src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py @@ -174,8 +174,6 @@ def validate_token( headers={"WWW-Authenticate": f'Bearer scope="{scope}"'}, ) - # if required_permissions: - # for perm in required_permissions: return payload @property From 27c8a7f0658c9cd66347562fe06cff45a8e0bb9f Mon Sep 17 00:00:00 2001 From: James Date: Tue, 5 Aug 2025 22:53:04 -0400 Subject: [PATCH 03/11] audience default --- src/stac_auth_proxy/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stac_auth_proxy/config.py b/src/stac_auth_proxy/config.py index 5c132a1f..49a0ce4e 100644 --- a/src/stac_auth_proxy/config.py +++ b/src/stac_auth_proxy/config.py @@ -39,7 +39,7 @@ class Settings(BaseSettings): upstream_url: HttpUrl oidc_discovery_url: HttpUrl oidc_discovery_internal_url: HttpUrl - audience: str + audience: str = None root_path: str = "" override_host: bool = True From 5527edcc142c91fdfa3dbc01ee98c038df59b566 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 5 Aug 2025 22:54:49 -0400 Subject: [PATCH 04/11] optional --- src/stac_auth_proxy/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stac_auth_proxy/config.py b/src/stac_auth_proxy/config.py index 49a0ce4e..27496238 100644 --- a/src/stac_auth_proxy/config.py +++ b/src/stac_auth_proxy/config.py @@ -39,7 +39,7 @@ class Settings(BaseSettings): upstream_url: HttpUrl oidc_discovery_url: HttpUrl oidc_discovery_internal_url: HttpUrl - audience: str = None + audience: Optional[str] = None root_path: str = "" override_host: bool = True From fc12a853574f38fba982357d7418a7e5261c817c Mon Sep 17 00:00:00 2001 From: James Date: Wed, 6 Aug 2025 10:29:31 -0400 Subject: [PATCH 05/11] audience error --- docker-compose.yaml | 1 - src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py | 8 +++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 551a4991..c31dd1ac 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -91,7 +91,6 @@ services: UPSTREAM_URL: ${UPSTREAM_URL:-http://stac:8001} OIDC_DISCOVERY_URL: ${OIDC_DISCOVERY_URL:-http://localhost:8888/.well-known/openid-configuration} OIDC_DISCOVERY_INTERNAL_URL: ${OIDC_DISCOVERY_INTERNAL_URL:-http://oidc:8888/.well-known/openid-configuration} - AUDIENCE: ${AUDIENCE} env_file: diff --git a/src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py b/src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py index 8f26b025..132f7a8d 100644 --- a/src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py +++ b/src/stac_auth_proxy/middleware/EnforceAuthMiddleware.py @@ -153,6 +153,13 @@ def validate_token( # NOTE: Audience validation MUST match audience claim if set in token (https://pyjwt.readthedocs.io/en/stable/changelog.html?highlight=audience#id40) audience=self.allowed_jwt_audiences, ) + except jwt.InvalidAudienceError as e: + logger.error("InvalidAudienceError: %r", e) + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate Audience", + headers={"WWW-Authenticate": "Bearer"}, + ) except ( jwt.exceptions.InvalidTokenError, jwt.exceptions.DecodeError, @@ -173,7 +180,6 @@ def validate_token( detail="Not enough permissions", headers={"WWW-Authenticate": f'Bearer scope="{scope}"'}, ) - return payload @property From 8d3ac43a98ba3948a250d6a55655fd97c7e0eff9 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 6 Aug 2025 13:20:31 -0400 Subject: [PATCH 06/11] handle mulltiple auds --- docs/user-guide/configuration.md | 6 +++--- src/stac_auth_proxy/app.py | 2 +- src/stac_auth_proxy/config.py | 17 +++++++++++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index ac738aaf..f7537f36 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -79,13 +79,13 @@ The application is configurable via environment variables. **Required:** No, defaults to the value of `OIDC_DISCOVERY_URL` **Example:** `http://auth/.well-known/openid-configuration` -### `AUDIENCE` +### `ALLOWED_JWT AUDIENCES` : The unique identifier of your API resource server - The AUDIENCE environment variable specifies the intended recipient of OAuth2 access tokens. This value represents the unique identifier of your API resource server and must match the `aud` (audience) claim present in incoming OAuth2 access tokens. If undefined the API will not impose a check on the `aud` claim + The ALLOWED_JWT AUDIENCES environment variable specifies the intended recipient of the JWT. This value represents the unique identifier of your API resource server and must match the `aud` (audience) claim present in incoming JWT. Multiple audiences can be defined by using a comma separated string. If undefined, the API will not impose a check on the `aud` claim -OAuth2 Audience Claim + `e.g. "https://auth.example.audience.1.net, https://auth.example.audience.2.net"` ### `DEFAULT_PUBLIC` diff --git a/src/stac_auth_proxy/app.py b/src/stac_auth_proxy/app.py index a456ef2a..a81c44ac 100644 --- a/src/stac_auth_proxy/app.py +++ b/src/stac_auth_proxy/app.py @@ -157,7 +157,7 @@ async def lifespan(app: FastAPI): private_endpoints=settings.private_endpoints, default_public=settings.default_public, oidc_discovery_url=settings.oidc_discovery_internal_url, - allowed_jwt_audiences=settings.audience, + allowed_jwt_audiences=settings.allowed_jwt_audiences, ) if settings.root_path or settings.upstream_url.path != "/": diff --git a/src/stac_auth_proxy/config.py b/src/stac_auth_proxy/config.py index 27496238..b565d876 100644 --- a/src/stac_auth_proxy/config.py +++ b/src/stac_auth_proxy/config.py @@ -3,7 +3,7 @@ import importlib from typing import Any, Literal, Optional, Sequence, TypeAlias, Union -from pydantic import BaseModel, Field, model_validator +from pydantic import BaseModel, Field, model_validator, field_validator from pydantic.networks import HttpUrl from pydantic_settings import BaseSettings, SettingsConfigDict @@ -16,6 +16,14 @@ _PREFIX_PATTERN = r"^/.*$" +def str2list(x: Optional[str] = None) -> Optional[Sequence[str]]: + """Convert string to list base on , delimiter.""" + if x: + return x.replace(" ", "").split(",") + + return None + + class _ClassInput(BaseModel): """Input model for dynamically loading a class or function.""" @@ -39,7 +47,7 @@ class Settings(BaseSettings): upstream_url: HttpUrl oidc_discovery_url: HttpUrl oidc_discovery_internal_url: HttpUrl - audience: Optional[str] = None + allowed_jwt_audiences: Optional[Sequence[str]] = None root_path: str = "" override_host: bool = True @@ -93,3 +101,8 @@ def _default_oidc_discovery_internal_url(cls, data: Any) -> Any: if not data.get("oidc_discovery_internal_url"): data["oidc_discovery_internal_url"] = data.get("oidc_discovery_url") return data + + @field_validator("allowed_jwt_audiences", mode="before") + @classmethod + def parse_audience(cls, v): + return str2list(v) From c68b5b3604a4a3c0c511510185c4e6727c18b338 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 6 Aug 2025 13:23:17 -0400 Subject: [PATCH 07/11] cleanup --- docker-compose.yaml | 2 -- docs/user-guide/configuration.md | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index c31dd1ac..867cadb8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -91,8 +91,6 @@ services: UPSTREAM_URL: ${UPSTREAM_URL:-http://stac:8001} OIDC_DISCOVERY_URL: ${OIDC_DISCOVERY_URL:-http://localhost:8888/.well-known/openid-configuration} OIDC_DISCOVERY_INTERNAL_URL: ${OIDC_DISCOVERY_INTERNAL_URL:-http://oidc:8888/.well-known/openid-configuration} - - env_file: - path: .env required: false diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index f7537f36..d629cabb 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -79,13 +79,13 @@ The application is configurable via environment variables. **Required:** No, defaults to the value of `OIDC_DISCOVERY_URL` **Example:** `http://auth/.well-known/openid-configuration` -### `ALLOWED_JWT AUDIENCES` +### `ALLOWED_JWT_AUDIENCES` : The unique identifier of your API resource server - The ALLOWED_JWT AUDIENCES environment variable specifies the intended recipient of the JWT. This value represents the unique identifier of your API resource server and must match the `aud` (audience) claim present in incoming JWT. Multiple audiences can be defined by using a comma separated string. If undefined, the API will not impose a check on the `aud` claim + The ALLOWED_JWT_AUDIENCES environment variable specifies the intended recipient of the JWT. This value represents the unique identifier of your API resource server and must match the `aud` (audience) claim present in the incoming JWT. Multiple audiences can be defined by using a comma separated string. If undefined, the API will not impose a check on the `aud` claim - `e.g. "https://auth.example.audience.1.net, https://auth.example.audience.2.net"` + `e.g. "https://auth.example.audience.1.net,https://auth.example.audience.2.net"` ### `DEFAULT_PUBLIC` From af51d285b81c990e8d7d2bc0626d89e5fd995e42 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 6 Aug 2025 17:26:51 -0400 Subject: [PATCH 08/11] tests --- src/stac_auth_proxy/config.py | 7 +- tests/test_authn.py | 129 ++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 3 deletions(-) diff --git a/src/stac_auth_proxy/config.py b/src/stac_auth_proxy/config.py index b565d876..e0a4349e 100644 --- a/src/stac_auth_proxy/config.py +++ b/src/stac_auth_proxy/config.py @@ -3,7 +3,7 @@ import importlib from typing import Any, Literal, Optional, Sequence, TypeAlias, Union -from pydantic import BaseModel, Field, model_validator, field_validator +from pydantic import BaseModel, Field, field_validator, model_validator from pydantic.networks import HttpUrl from pydantic_settings import BaseSettings, SettingsConfigDict @@ -17,7 +17,7 @@ def str2list(x: Optional[str] = None) -> Optional[Sequence[str]]: - """Convert string to list base on , delimiter.""" + """Convert string to list based on , delimiter.""" if x: return x.replace(" ", "").split(",") @@ -104,5 +104,6 @@ def _default_oidc_discovery_internal_url(cls, data: Any) -> Any: @field_validator("allowed_jwt_audiences", mode="before") @classmethod - def parse_audience(cls, v): + def parse_audience(cls, v) -> Optional[Sequence[str]]: + """Parse a comma separated string list of audiences into a list.""" return str2list(v) diff --git a/tests/test_authn.py b/tests/test_authn.py index 2a18f8f8..8ef86fce 100644 --- a/tests/test_authn.py +++ b/tests/test_authn.py @@ -342,3 +342,132 @@ def test_options_requests_with_cors_headers(source_api_server): assert ( response.status_code == 200 ), "OPTIONS request with CORS headers should succeed" + + +@pytest.mark.parametrize( + "token_audiences,allowed_audiences,expected_status", + [ + # Single audience scenarios + (["stac-api"], "stac-api", 200), + (["stac-api"], "different-api", 401), + (["stac-api"], "stac-api,other-api", 200), + # Multiple audiences in token + (["stac-api", "other-api"], "stac-api", 200), + (["stac-api", "other-api"], "other-api", 200), + (["stac-api", "other-api"], "different-api", 401), + (["stac-api", "other-api"], "stac-api, other-api,third-api", 200), + # No audience in token + (None, "stac-api", 401), + ("", "stac-api", 401), + # Empty allowed audiences will regect tokens with an `aud` claim + ("any-api", "", 401), + ("any-api", None, 401), + # Backward compatibility - no audience configured + (None, None, 200), + ("", None, 200), + ], +) +def test_jwt_audience_validation( + source_api_server, + token_builder, + token_audiences, + allowed_audiences, + expected_status, +): + """Test JWT audience validation with various configurations.""" + # Build app with audience configuration + app_factory = AppFactory( + oidc_discovery_url="https://example-stac-api.com/.well-known/openid-configuration", + default_public=False, + allowed_jwt_audiences=allowed_audiences, + ) + test_app = app_factory(upstream_url=source_api_server) + + # Build token with audience claim + token_payload = {} + if token_audiences is not None: + token_payload["aud"] = token_audiences + + valid_auth_token = token_builder(token_payload) + + client = TestClient(test_app) + response = client.get( + "/collections", + headers={"Authorization": f"Bearer {valid_auth_token}"}, + ) + assert response.status_code == expected_status + + +def test_audience_validation_with_scopes(source_api_server, token_builder): + """Test that audience validation works alongside scope validation.""" + app_factory = AppFactory( + oidc_discovery_url="https://example-stac-api.com/.well-known/openid-configuration", + default_public=False, + allowed_jwt_audiences="stac-api", + private_endpoints={r"^/collections$": [("POST", "collection:create")]}, + ) + test_app = app_factory(upstream_url=source_api_server) + + client = TestClient(test_app) + + # Valid audience but missing scope + token = token_builder({"aud": ["stac-api"], "scope": "openid"}) + response = client.post( + "/collections", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 401 # Missing required scope + + # Valid audience and valid scope + token = token_builder({"aud": ["stac-api"], "scope": "collection:create"}) + response = client.post( + "/collections", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 200 + + # Invalid audience but valid scope + token = token_builder({"aud": ["wrong-api"], "scope": "collection:create"}) + response = client.post( + "/collections", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 401 # Wrong audience + + +@pytest.mark.parametrize( + "allowed_audiences_config,test_audience,expected_status", + [ + # Comma-separated string + ("stac-api,other-api", "stac-api", 200), + ("stac-api,other-api", "other-api", 200), + ("stac-api,other-api", "unknown-api", 401), + # Comma-separated with spaces + ("stac-api, other-api", "stac-api", 200), + ("stac-api, other-api", "other-api", 200), + ("stac-api, other-api", "unknown-api", 401), + ], +) +def test_allowed_audiences_configuration_formats( + source_api_server, + token_builder, + allowed_audiences_config, + test_audience, + expected_status, +): + """Test different configuration formats for ALLOWED_JWT_AUDIENCES.""" + app_factory = AppFactory( + oidc_discovery_url="https://example-stac-api.com/.well-known/openid-configuration", + default_public=False, + allowed_jwt_audiences=allowed_audiences_config, + ) + test_app = app_factory(upstream_url=source_api_server) + + client = TestClient(test_app) + + token = token_builder({"aud": [test_audience]}) + response = client.get( + "/collections", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == expected_status From b947ac3bcb7efa494a513947ab54d1638934f437 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 6 Aug 2025 17:31:04 -0400 Subject: [PATCH 09/11] param --- tests/test_authn.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/tests/test_authn.py b/tests/test_authn.py index 8ef86fce..39473e59 100644 --- a/tests/test_authn.py +++ b/tests/test_authn.py @@ -398,7 +398,17 @@ def test_jwt_audience_validation( assert response.status_code == expected_status -def test_audience_validation_with_scopes(source_api_server, token_builder): +@pytest.mark.parametrize( + "aud_value,scope,expected_status,description", + [ + (["stac-api"], "openid", 401, "Valid audience but missing scope"), + (["stac-api"], "collection:create", 200, "Valid audience and valid scope"), + (["wrong-api"], "collection:create", 401, "Invalid audience but valid scope"), + ], +) +def test_audience_validation_with_scopes( + source_api_server, token_builder, aud_value, scope, expected_status, description +): """Test that audience validation works alongside scope validation.""" app_factory = AppFactory( oidc_discovery_url="https://example-stac-api.com/.well-known/openid-configuration", @@ -410,29 +420,12 @@ def test_audience_validation_with_scopes(source_api_server, token_builder): client = TestClient(test_app) - # Valid audience but missing scope - token = token_builder({"aud": ["stac-api"], "scope": "openid"}) + token = token_builder({"aud": aud_value, "scope": scope}) response = client.post( "/collections", headers={"Authorization": f"Bearer {token}"}, ) - assert response.status_code == 401 # Missing required scope - - # Valid audience and valid scope - token = token_builder({"aud": ["stac-api"], "scope": "collection:create"}) - response = client.post( - "/collections", - headers={"Authorization": f"Bearer {token}"}, - ) - assert response.status_code == 200 - - # Invalid audience but valid scope - token = token_builder({"aud": ["wrong-api"], "scope": "collection:create"}) - response = client.post( - "/collections", - headers={"Authorization": f"Bearer {token}"}, - ) - assert response.status_code == 401 # Wrong audience + assert response.status_code == expected_status @pytest.mark.parametrize( From 7435e0c8eeb2be22dde36709489a7e4b056dabc4 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 7 Aug 2025 12:14:47 -0400 Subject: [PATCH 10/11] docs --- docs/user-guide/configuration.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index d629cabb..6bb9623c 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -81,11 +81,13 @@ The application is configurable via environment variables. ### `ALLOWED_JWT_AUDIENCES` -: The unique identifier of your API resource server +: Unique identifiers of your API resource server(s) - The ALLOWED_JWT_AUDIENCES environment variable specifies the intended recipient of the JWT. This value represents the unique identifier of your API resource server and must match the `aud` (audience) claim present in the incoming JWT. Multiple audiences can be defined by using a comma separated string. If undefined, the API will not impose a check on the `aud` claim + **Type:** string + **Required:** No + **Example:** `https://auth.example.audience.1.net,https://auth.example.audience.2.net` + **Note** A comma-separated list of the intended recipient of the JWT. At least one audience value must match the `aud` (audience) claim present in the incoming JWT. If undefined, the API will not impose a check on the `aud` claim - `e.g. "https://auth.example.audience.1.net,https://auth.example.audience.2.net"` ### `DEFAULT_PUBLIC` From e8fdc8840d56b23d1f915a65b13897122ac4ef57 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 7 Aug 2025 12:17:36 -0400 Subject: [PATCH 11/11] docs --- docs/user-guide/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index 6bb9623c..10cb4e9c 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -81,12 +81,12 @@ The application is configurable via environment variables. ### `ALLOWED_JWT_AUDIENCES` -: Unique identifiers of your API resource server(s) +: Unique identifier(s) of API resource server(s) **Type:** string **Required:** No **Example:** `https://auth.example.audience.1.net,https://auth.example.audience.2.net` - **Note** A comma-separated list of the intended recipient of the JWT. At least one audience value must match the `aud` (audience) claim present in the incoming JWT. If undefined, the API will not impose a check on the `aud` claim + **Note** A comma-separated list of the intended recipient(s) of the JWT. At least one audience value must match the `aud` (audience) claim present in the incoming JWT. If undefined, the API will not impose a check on the `aud` claim ### `DEFAULT_PUBLIC`