Skip to content

Commit ed964bc

Browse files
committed
more changes
1 parent 6fdcb59 commit ed964bc

File tree

6 files changed

+132
-9
lines changed

6 files changed

+132
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
## [0.19.0] - 2024-05-06
1212

1313
- `create_new_session` now defaults to the value of the `st-auth-mode` header (if available) if the configured `get_token_transfer_method` returns `any`.
14+
- Enable smooth switching between `use_dynamic_access_token_signing_key` settings by allowing refresh calls to change the signing key type of a session.
1415

1516
## [0.18.11] - 2024-04-26
1617

supertokens_python/recipe/session/recipe_implementation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ async def refresh_session(
297297
refresh_token,
298298
anti_csrf_token,
299299
disable_anti_csrf,
300+
self.config.use_dynamic_access_token_signing_key,
300301
user_context=user_context,
301302
)
302303

supertokens_python/recipe/session/session_functions.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ async def get_session(
218218
)
219219

220220
raise_try_refresh_token_exception(
221-
"The access token doesn't match the useDynamicAccessTokenSigningKey setting"
221+
"The access token doesn't match the use_dynamic_access_token_signing_key setting"
222222
)
223223

224224
# If we get here we either have a V2 token that doesn't pass verification or a valid V3> token
@@ -308,13 +308,15 @@ async def get_session(
308308
response["session"].get("tenantId")
309309
or (access_token_info or {}).get("tenantId"),
310310
),
311-
GetSessionAPIResponseAccessToken(
312-
response["accessToken"]["token"],
313-
response["accessToken"]["expiry"],
314-
response["accessToken"]["createdTime"],
315-
)
316-
if "accessToken" in response
317-
else None,
311+
(
312+
GetSessionAPIResponseAccessToken(
313+
response["accessToken"]["token"],
314+
response["accessToken"]["expiry"],
315+
response["accessToken"]["createdTime"],
316+
)
317+
if "accessToken" in response
318+
else None
319+
),
318320
)
319321
if response["status"] == "UNAUTHORISED":
320322
log_debug_message("getSession: Returning UNAUTHORISED because of core response")
@@ -331,6 +333,7 @@ async def refresh_session(
331333
refresh_token: str,
332334
anti_csrf_token: Union[str, None],
333335
disable_anti_csrf: bool,
336+
use_dynamic_access_token_signing_key: bool,
334337
user_context: Optional[Dict[str, Any]],
335338
) -> CreateOrRefreshAPIResponse:
336339
data = {
@@ -339,6 +342,7 @@ async def refresh_session(
339342
not disable_anti_csrf
340343
and recipe_implementation.config.anti_csrf_function_or_string == "VIA_TOKEN"
341344
),
345+
"useDynamicSigningKey": use_dynamic_access_token_signing_key,
342346
}
343347

344348
if anti_csrf_token is not None:

tests/sessions/test_jwks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -698,5 +698,5 @@ async def test_session_verification_of_jwt_with_dynamic_signing_key_mode_works_a
698698
except TryRefreshTokenError as e:
699699
assert (
700700
str(e)
701-
== "The access token doesn't match the useDynamicAccessTokenSigningKey setting"
701+
== "The access token doesn't match the use_dynamic_access_token_signing_key setting"
702702
)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import pytest
2+
from supertokens_python import init
3+
from supertokens_python.recipe import session
4+
from supertokens_python.recipe.session.asyncio import (
5+
create_new_session_without_request_response,
6+
get_session_without_request_response,
7+
refresh_session_without_request_response,
8+
)
9+
from supertokens_python.recipe.session.session_class import SessionContainer
10+
from tests.utils import (
11+
get_st_init_args,
12+
setup_function,
13+
start_st,
14+
teardown_function,
15+
reset,
16+
)
17+
from supertokens_python.recipe.session.jwt import (
18+
parse_jwt_without_signature_verification,
19+
)
20+
from supertokens_python.recipe.session.interfaces import GetSessionTokensDangerouslyDict
21+
22+
_ = setup_function # type:ignore
23+
_ = teardown_function # type:ignore
24+
25+
pytestmark = pytest.mark.asyncio
26+
27+
28+
async def test_dynamic_key_switching():
29+
init(**get_st_init_args([session.init(use_dynamic_access_token_signing_key=True)]))
30+
start_st()
31+
32+
# Create a new session without an actual HTTP request-response flow
33+
create_res: SessionContainer = await create_new_session_without_request_response(
34+
"public", "test-user-id", {"tokenProp": True}, {"dbProp": True}
35+
)
36+
37+
# Extract session tokens for further testing
38+
tokens = create_res.get_all_session_tokens_dangerously()
39+
check_access_token_signing_key_type(tokens, True)
40+
41+
# Reset and reinitialize with dynamic signing key disabled
42+
reset(stop_core=False)
43+
init(**get_st_init_args([session.init(use_dynamic_access_token_signing_key=False)]))
44+
45+
caught_exception = None
46+
try:
47+
# Attempt to retrieve the session using previously obtained tokens
48+
await get_session_without_request_response(
49+
tokens["accessToken"], tokens["antiCsrfToken"]
50+
)
51+
except Exception as e:
52+
caught_exception = e
53+
54+
# Check for the expected exception due to token signing key mismatch
55+
assert (
56+
caught_exception is not None
57+
), "Expected an exception to be thrown, but none was."
58+
assert (
59+
str(caught_exception)
60+
== "The access token doesn't match the use_dynamic_access_token_signing_key setting"
61+
), f"Unexpected exception message: {str(caught_exception)}"
62+
63+
64+
async def test_refresh_session():
65+
init(**get_st_init_args([session.init(use_dynamic_access_token_signing_key=True)]))
66+
start_st()
67+
68+
# Create a new session without an actual HTTP request-response flow
69+
create_res: SessionContainer = await create_new_session_without_request_response(
70+
"public", "test-user-id", {"tokenProp": True}, {"dbProp": True}
71+
)
72+
73+
# Extract session tokens for further testing
74+
tokens = create_res.get_all_session_tokens_dangerously()
75+
check_access_token_signing_key_type(tokens, True)
76+
77+
# Reset and reinitialize with dynamic signing key disabled
78+
reset(stop_core=False)
79+
init(**get_st_init_args([session.init(use_dynamic_access_token_signing_key=False)]))
80+
81+
assert tokens["refreshToken"] is not None
82+
83+
# Refresh session
84+
refreshed_session = await refresh_session_without_request_response(
85+
tokens["refreshToken"], True, tokens["antiCsrfToken"]
86+
)
87+
tokens_after_refresh = refreshed_session.get_all_session_tokens_dangerously()
88+
assert tokens_after_refresh["accessAndFrontTokenUpdated"] is True
89+
check_access_token_signing_key_type(tokens_after_refresh, False)
90+
91+
# Verify session after refresh
92+
verified_session = await get_session_without_request_response(
93+
tokens_after_refresh["accessToken"], tokens_after_refresh["antiCsrfToken"]
94+
)
95+
assert verified_session is not None
96+
tokens_after_verify = verified_session.get_all_session_tokens_dangerously()
97+
assert tokens_after_verify["accessAndFrontTokenUpdated"] is True
98+
check_access_token_signing_key_type(tokens_after_verify, False)
99+
100+
# Verify session again
101+
verified2_session = await get_session_without_request_response(
102+
tokens_after_verify["accessToken"], tokens_after_verify["antiCsrfToken"]
103+
)
104+
assert verified2_session is not None
105+
tokens_after_verify2 = verified2_session.get_all_session_tokens_dangerously()
106+
assert tokens_after_verify2["accessAndFrontTokenUpdated"] is False
107+
108+
109+
def check_access_token_signing_key_type(
110+
tokens: GetSessionTokensDangerouslyDict, is_dynamic: bool
111+
):
112+
info = parse_jwt_without_signature_verification(tokens["accessToken"])
113+
if is_dynamic:
114+
assert info.kid is not None and info.kid.startswith("d-")
115+
else:
116+
assert info.kid is not None and info.kid.startswith("s-")

tests/test_session.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ async def test_that_once_the_info_is_loaded_it_doesnt_query_again():
119119
response.refreshToken.token,
120120
response.antiCsrfToken,
121121
False,
122+
True,
122123
None,
123124
)
124125

0 commit comments

Comments
 (0)