Skip to content

Commit 080e330

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 080e330

File tree

2 files changed

+110
-31
lines changed

2 files changed

+110
-31
lines changed

tests/test_session.py

Lines changed: 99 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,33 @@ 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"]
167+
| {
168+
"aud": TEST_CONSTANTS["CLIENT_ID"],
169+
},
170+
TEST_CONSTANTS["PRIVATE_KEY"],
171+
algorithm="RS256",
172+
)
173+
174+
session_data = Session.seal_data(
175+
{"access_token": access_token, "user": TEST_CONSTANTS["TEST_USER"]},
176+
TEST_CONSTANTS["COOKIE_PASSWORD"],
177+
)
178+
session = Session(
179+
user_management=mock_user_management,
180+
client_id=TEST_CONSTANTS["CLIENT_ID"],
181+
session_data=session_data,
182+
cookie_password=TEST_CONSTANTS["COOKIE_PASSWORD"],
183+
)
184+
185+
response = session.authenticate()
186+
187+
assert isinstance(response, AuthenticateWithSessionCookieSuccessResponse)
188+
189+
148190
@with_jwks_mock
149191
def test_authenticate_success(TEST_CONSTANTS, mock_user_management):
150192
session = Session(
@@ -229,27 +271,16 @@ def test_refresh_invalid_session_cookie(TEST_CONSTANTS, mock_user_management):
229271

230272
@with_jwks_mock
231273
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-
243274
session_data = Session.seal_data(
244-
{"refresh_token": "refresh_token_12345", "user": test_user},
275+
{"refresh_token": "refresh_token_12345", "user": TEST_CONSTANTS["TEST_USER"]},
245276
TEST_CONSTANTS["COOKIE_PASSWORD"],
246277
)
247278

248279
mock_response = {
249280
"access_token": TEST_CONSTANTS["TEST_TOKEN"],
250281
"refresh_token": "refresh_token_123",
251282
"sealed_session": session_data,
252-
"user": test_user,
283+
"user": TEST_CONSTANTS["TEST_USER"],
253284
}
254285

255286
mock_user_management.authenticate_with_refresh_token.return_value = (
@@ -278,7 +309,7 @@ def test_refresh_success(TEST_CONSTANTS, mock_user_management):
278309

279310
assert isinstance(response, RefreshWithSessionCookieSuccessResponse)
280311
assert response.authenticated is True
281-
assert response.user.id == test_user["id"]
312+
assert response.user.id == TEST_CONSTANTS["TEST_USER"]["id"]
282313

283314
# Verify the refresh token was used correctly
284315
mock_user_management.authenticate_with_refresh_token.assert_called_once_with(
@@ -291,6 +322,45 @@ def test_refresh_success(TEST_CONSTANTS, mock_user_management):
291322
)
292323

293324

325+
@with_jwks_mock
326+
def test_refresh_success_with_aud_claim(TEST_CONSTANTS, mock_user_management):
327+
session_data = Session.seal_data(
328+
{"refresh_token": "refresh_token_12345", "user": TEST_CONSTANTS["TEST_USER"]},
329+
TEST_CONSTANTS["COOKIE_PASSWORD"],
330+
)
331+
332+
access_token = jwt.encode(
333+
TEST_CONSTANTS["TEST_TOKEN_CLAIMS"]
334+
| {
335+
"aud": TEST_CONSTANTS["CLIENT_ID"],
336+
},
337+
TEST_CONSTANTS["PRIVATE_KEY"],
338+
algorithm="RS256",
339+
)
340+
341+
mock_response = {
342+
"access_token": access_token,
343+
"refresh_token": "refresh_token_123",
344+
"sealed_session": session_data,
345+
"user": TEST_CONSTANTS["TEST_USER"],
346+
}
347+
348+
mock_user_management.authenticate_with_refresh_token.return_value = (
349+
RefreshTokenAuthenticationResponse(**mock_response)
350+
)
351+
352+
session = Session(
353+
user_management=mock_user_management,
354+
client_id=TEST_CONSTANTS["CLIENT_ID"],
355+
session_data=session_data,
356+
cookie_password=TEST_CONSTANTS["COOKIE_PASSWORD"],
357+
)
358+
359+
response = session.refresh()
360+
361+
assert isinstance(response, RefreshWithSessionCookieSuccessResponse)
362+
363+
294364
def test_seal_data(TEST_CONSTANTS):
295365
test_data = {"test": "data"}
296366
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)