Skip to content

Commit 2e746a9

Browse files
committed
fix: Don't validate aud claim
Currently, the `aud` claim is optional (gated by a feature flag), so there's no reason to validate the claim yet, even when it's present.
1 parent 5f8eb45 commit 2e746a9

File tree

2 files changed

+104
-31
lines changed

2 files changed

+104
-31
lines changed

tests/test_session.py

Lines changed: 93 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,44 @@ def TEST_CONSTANTS():
3434
encryption_algorithm=serialization.NoEncryption(),
3535
)
3636

37+
current_datetime = datetime.now(timezone.utc)
38+
current_timestamp = str(current_datetime)
39+
40+
token_claims = {
41+
"sid": "session_123",
42+
"org_id": "organization_123",
43+
"role": "admin",
44+
"permissions": ["read"],
45+
"entitlements": ["feature_1"],
46+
"exp": int(current_datetime.timestamp()) + 3600,
47+
"iat": int(current_datetime.timestamp()),
48+
}
49+
50+
user_id = "user_123"
51+
3752
return {
3853
"COOKIE_PASSWORD": "pfSqwTFXUTGEBBD1RQh2kt/oNJYxBgaoZan4Z8sMrKU=",
3954
"SESSION_DATA": "session_data",
4055
"CLIENT_ID": "client_123",
41-
"USER_ID": "user_123",
56+
"USER_ID": user_id,
4257
"SESSION_ID": "session_123",
4358
"ORGANIZATION_ID": "organization_123",
44-
"CURRENT_TIMESTAMP": str(datetime.now(timezone.utc)),
59+
"CURRENT_DATETIME": current_datetime,
60+
"CURRENT_TIMESTAMP": current_timestamp,
4561
"PRIVATE_KEY": private_pem,
4662
"PUBLIC_KEY": public_key,
47-
"TEST_TOKEN": jwt.encode(
48-
{
49-
"sid": "session_123",
50-
"org_id": "organization_123",
51-
"role": "admin",
52-
"permissions": ["read"],
53-
"entitlements": ["feature_1"],
54-
"exp": int(datetime.now(timezone.utc).timestamp()) + 3600,
55-
"iat": int(datetime.now(timezone.utc).timestamp()),
56-
},
57-
private_pem,
58-
algorithm="RS256",
59-
),
63+
"TEST_TOKEN": jwt.encode(token_claims, private_pem, algorithm="RS256"),
64+
"TEST_TOKEN_CLAIMS": token_claims,
65+
"TEST_USER": {
66+
"object": "user",
67+
"id": user_id,
68+
"email": "[email protected]",
69+
"first_name": "Test",
70+
"last_name": "User",
71+
"email_verified": True,
72+
"created_at": current_timestamp,
73+
"updated_at": current_timestamp,
74+
},
6075
}
6176

6277

@@ -145,6 +160,30 @@ def test_authenticate_invalid_jwt(TEST_CONSTANTS, mock_user_management):
145160
assert response.reason == AuthenticateWithSessionCookieFailureReason.INVALID_JWT
146161

147162

163+
@with_jwks_mock
164+
def test_authenticate_jwt_with_aud_claim(TEST_CONSTANTS, mock_user_management):
165+
access_token = jwt.encode(
166+
{**TEST_CONSTANTS["TEST_TOKEN_CLAIMS"], **{"aud": TEST_CONSTANTS["CLIENT_ID"]}},
167+
TEST_CONSTANTS["PRIVATE_KEY"],
168+
algorithm="RS256",
169+
)
170+
171+
session_data = Session.seal_data(
172+
{"access_token": access_token, "user": TEST_CONSTANTS["TEST_USER"]},
173+
TEST_CONSTANTS["COOKIE_PASSWORD"],
174+
)
175+
session = Session(
176+
user_management=mock_user_management,
177+
client_id=TEST_CONSTANTS["CLIENT_ID"],
178+
session_data=session_data,
179+
cookie_password=TEST_CONSTANTS["COOKIE_PASSWORD"],
180+
)
181+
182+
response = session.authenticate()
183+
184+
assert isinstance(response, AuthenticateWithSessionCookieSuccessResponse)
185+
186+
148187
@with_jwks_mock
149188
def test_authenticate_success(TEST_CONSTANTS, mock_user_management):
150189
session = Session(
@@ -229,27 +268,16 @@ def test_refresh_invalid_session_cookie(TEST_CONSTANTS, mock_user_management):
229268

230269
@with_jwks_mock
231270
def test_refresh_success(TEST_CONSTANTS, mock_user_management):
232-
test_user = {
233-
"object": "user",
234-
"id": TEST_CONSTANTS["USER_ID"],
235-
"email": "[email protected]",
236-
"first_name": "Test",
237-
"last_name": "User",
238-
"email_verified": True,
239-
"created_at": TEST_CONSTANTS["CURRENT_TIMESTAMP"],
240-
"updated_at": TEST_CONSTANTS["CURRENT_TIMESTAMP"],
241-
}
242-
243271
session_data = Session.seal_data(
244-
{"refresh_token": "refresh_token_12345", "user": test_user},
272+
{"refresh_token": "refresh_token_12345", "user": TEST_CONSTANTS["TEST_USER"]},
245273
TEST_CONSTANTS["COOKIE_PASSWORD"],
246274
)
247275

248276
mock_response = {
249277
"access_token": TEST_CONSTANTS["TEST_TOKEN"],
250278
"refresh_token": "refresh_token_123",
251279
"sealed_session": session_data,
252-
"user": test_user,
280+
"user": TEST_CONSTANTS["TEST_USER"],
253281
}
254282

255283
mock_user_management.authenticate_with_refresh_token.return_value = (
@@ -278,7 +306,7 @@ def test_refresh_success(TEST_CONSTANTS, mock_user_management):
278306

279307
assert isinstance(response, RefreshWithSessionCookieSuccessResponse)
280308
assert response.authenticated is True
281-
assert response.user.id == test_user["id"]
309+
assert response.user.id == TEST_CONSTANTS["TEST_USER"]["id"]
282310

283311
# Verify the refresh token was used correctly
284312
mock_user_management.authenticate_with_refresh_token.assert_called_once_with(
@@ -291,6 +319,42 @@ def test_refresh_success(TEST_CONSTANTS, mock_user_management):
291319
)
292320

293321

322+
@with_jwks_mock
323+
def test_refresh_success_with_aud_claim(TEST_CONSTANTS, mock_user_management):
324+
session_data = Session.seal_data(
325+
{"refresh_token": "refresh_token_12345", "user": TEST_CONSTANTS["TEST_USER"]},
326+
TEST_CONSTANTS["COOKIE_PASSWORD"],
327+
)
328+
329+
access_token = jwt.encode(
330+
{**TEST_CONSTANTS["TEST_TOKEN_CLAIMS"], **{"aud": TEST_CONSTANTS["CLIENT_ID"]}},
331+
TEST_CONSTANTS["PRIVATE_KEY"],
332+
algorithm="RS256",
333+
)
334+
335+
mock_response = {
336+
"access_token": access_token,
337+
"refresh_token": "refresh_token_123",
338+
"sealed_session": session_data,
339+
"user": TEST_CONSTANTS["TEST_USER"],
340+
}
341+
342+
mock_user_management.authenticate_with_refresh_token.return_value = (
343+
RefreshTokenAuthenticationResponse(**mock_response)
344+
)
345+
346+
session = Session(
347+
user_management=mock_user_management,
348+
client_id=TEST_CONSTANTS["CLIENT_ID"],
349+
session_data=session_data,
350+
cookie_password=TEST_CONSTANTS["COOKIE_PASSWORD"],
351+
)
352+
353+
response = session.refresh()
354+
355+
assert isinstance(response, RefreshWithSessionCookieSuccessResponse)
356+
357+
294358
def test_seal_data(TEST_CONSTANTS):
295359
test_data = {"test": "data"}
296360
sealed = Session.seal_data(test_data, TEST_CONSTANTS["COOKIE_PASSWORD"])

workos/session.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ def authenticate(
7979

8080
signing_key = self.jwks.get_signing_key_from_jwt(session["access_token"])
8181
decoded = jwt.decode(
82-
session["access_token"], signing_key.key, algorithms=self.jwk_algorithms
82+
session["access_token"],
83+
signing_key.key,
84+
algorithms=self.jwk_algorithms,
85+
options={"verify_aud": False},
8386
)
8487

8588
return AuthenticateWithSessionCookieSuccessResponse(
@@ -141,6 +144,7 @@ def refresh(
141144
auth_response.access_token,
142145
signing_key.key,
143146
algorithms=self.jwk_algorithms,
147+
options={"verify_aud": False},
144148
)
145149

146150
return RefreshWithSessionCookieSuccessResponse(
@@ -176,7 +180,12 @@ def get_logout_url(self, return_to: Optional[str] = None) -> str:
176180
def _is_valid_jwt(self, token: str) -> bool:
177181
try:
178182
signing_key = self.jwks.get_signing_key_from_jwt(token)
179-
jwt.decode(token, signing_key.key, algorithms=self.jwk_algorithms)
183+
jwt.decode(
184+
token,
185+
signing_key.key,
186+
algorithms=self.jwk_algorithms,
187+
options={"verify_aud": False},
188+
)
180189
return True
181190
except jwt.exceptions.InvalidTokenError:
182191
return False

0 commit comments

Comments
 (0)