Skip to content

Commit 972d256

Browse files
authored
Add support for configurable audience (HarryMWinters#15) 🔮
- Adds optional audience argument to get_auth. - Makes audience default to Client ID when unspecified. - Adds tests for new functionality. - Updates docs.
1 parent 6db40e0 commit 972d256

File tree

3 files changed

+67
-2
lines changed

3 files changed

+67
-2
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ from fastapi_oidc import get_auth
4545

4646
OIDC_config = {
4747
"client_id": "0oa1e3pv9opbyq2Gm4x7",
48+
# Audience can be omitted in which case the aud value defaults to client_id
49+
"audience": "https://yourapi.url.com/api",
4850
"base_authorization_server_uri": "https://dev-126594.okta.com",
4951
"issuer": "dev-126594.okta.com",
5052
"signature_cache_ttl": 3600,

fastapi_oidc/auth.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def test_auth(authenticated_user: AuthenticatedUser = Depends(authenticate_user)
1717
"""
1818

1919
from typing import Callable
20+
from typing import Optional
2021

2122
from fastapi import Depends
2223
from fastapi import HTTPException
@@ -33,6 +34,7 @@ def test_auth(authenticated_user: AuthenticatedUser = Depends(authenticate_user)
3334
def get_auth(
3435
*_,
3536
client_id: str,
37+
audience: Optional[str] = None,
3638
base_authorization_server_uri: str,
3739
issuer: str,
3840
signature_cache_ttl: int,
@@ -44,6 +46,8 @@ def get_auth(
4446
4547
Args:
4648
client_id (str): This string is provided when you register with your resource server.
49+
audience (str): (Optional) The audience string configured by your auth server.
50+
If not set defaults to client_id
4751
base_authorization_server_uri(URL): Everything before /.wellknow in your auth server URL.
4852
I.E. https://dev-123456.okta.com
4953
issuer (URL): Same as base_authorization. This is used to generating OpenAPI3.0 docs which
@@ -94,8 +98,7 @@ def authenticate_user(auth_header: str = Depends(oauth2_scheme)) -> IDToken:
9498
id_token,
9599
key,
96100
algorithms,
97-
# TODO Check that client ID will always equal audience
98-
audience=client_id,
101+
audience=audience if audience else client_id,
99102
issuer=issuer,
100103
# Disabled at_hash check since we aren't using the access token
101104
options={"verify_at_hash": False},

tests/test_auth.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ class Fixtures:
3535

3636

3737
TEST_CONFIG = {
38+
"client_id": "CongenitalOptimist",
39+
"audience": "NeverAgain",
40+
"base_authorization_server_uri": "WhatAreTheCivilianApplications?",
41+
"issuer": "PokeItWithAStick",
42+
"signature_cache_ttl": 6e3,
43+
}
44+
45+
# Test configuration without audience
46+
TEST_CONFIG_NO_AUD = {
3847
"client_id": "CongenitalOptimist",
3948
"base_authorization_server_uri": "WhatAreTheCivilianApplications?",
4049
"issuer": "PokeItWithAStick",
@@ -46,7 +55,39 @@ def _make_token(
4655
email: str,
4756
private_key: str = Fixtures.TESTING_PRIVATE_KEY,
4857
client_id: str = str(TEST_CONFIG["client_id"]),
58+
audience: str = str(TEST_CONFIG["audience"]),
4959
issuer: str = str(TEST_CONFIG["issuer"]),
60+
) -> str:
61+
now = int(time.time())
62+
return jwt.encode(
63+
{
64+
"aud": audience,
65+
"iss": issuer,
66+
"email": email,
67+
"name": "SweetAndFullOfGrace",
68+
"preferred_username": "Sweet",
69+
"exp": now + 30,
70+
"auth_time": now,
71+
"sub": "foo",
72+
"ver": "1",
73+
"iat": now,
74+
"jti": str(uuid.uuid4()),
75+
"amr": [],
76+
"idp": "",
77+
"nonce": "",
78+
"at_hash": "",
79+
},
80+
private_key,
81+
algorithm="RS256",
82+
).decode("UTF-8")
83+
84+
85+
# Make a token where audience is client_id
86+
def _make_token_no_aud(
87+
email: str,
88+
private_key: str = Fixtures.TESTING_PRIVATE_KEY,
89+
client_id: str = str(TEST_CONFIG_NO_AUD["client_id"]),
90+
issuer: str = str(TEST_CONFIG_NO_AUD["issuer"]),
5091
) -> str:
5192
now = int(time.time())
5293
return jwt.encode(
@@ -87,3 +128,22 @@ class functions:
87128
authenticate_user = auth.get_auth(**TEST_CONFIG)
88129
IDToken = authenticate_user(auth_header=f"Bearer {token}")
89130
assert IDToken.email == email # nosec
131+
132+
133+
# Ensure that when no audience is supplied, that the audience defaults to client ID
134+
def test__authenticate_user_no_aud(monkeypatch):
135+
def mock_discovery(*args, **kwargs):
136+
class functions:
137+
auth_server = lambda **_: Fixtures.OIDC_DISCOVERY_RESPONSE
138+
public_keys = lambda _: Fixtures.TESTING_PUBLIC_KEY
139+
signing_algos = lambda x: x["id_token_signing_alg_values_supported"]
140+
141+
return functions
142+
143+
monkeypatch.setattr(auth.discovery, "configure", mock_discovery)
144+
email = "AnticipationOfANewLoversArrivalThe@VeryLittleGravitasIndeed"
145+
token = _make_token_no_aud(email=email)
146+
authenticate_user = auth.get_auth(**TEST_CONFIG_NO_AUD)
147+
IDToken = authenticate_user(auth_header=f"Bearer {token}")
148+
assert IDToken.email == email # nosec
149+
assert IDToken.aud == TEST_CONFIG_NO_AUD["client_id"]

0 commit comments

Comments
 (0)