Skip to content

Commit adcfc6b

Browse files
feat: propagate client claim through token refresh
1 parent 7cfef43 commit adcfc6b

2 files changed

Lines changed: 79 additions & 1 deletion

File tree

src/apps/authentication/api/auth.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,13 +687,19 @@ async def refresh_access_token(
687687

688688
rjti = str(uuid.uuid4())
689689
refresh_token = AuthenticationService.create_refresh_token(
690-
{JWTClaim.sub: str(user_id), JWTClaim.jti: rjti, JWTClaim.exp: token_data.exp}
690+
{
691+
JWTClaim.sub: str(user_id),
692+
JWTClaim.jti: rjti,
693+
JWTClaim.exp: token_data.exp,
694+
**client_token_claims(token_data.client),
695+
}
691696
)
692697

693698
access_token = AuthenticationService.create_access_token(
694699
{
695700
JWTClaim.sub: str(user_id),
696701
JWTClaim.rjti: rjti,
702+
**client_token_claims(token_data.client),
697703
}
698704
)
699705
except BaseError as e:

src/apps/authentication/tests/test_auth.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,78 @@ async def test_refresh_access_token(self, client: TestClient, user: User, mocker
142142
assert event.event_outcome == EventOutcome.SUCCESS
143143
assert response.status_code == http.HTTPStatus.OK
144144

145+
async def test_refresh_access_token__propagates_client_claim(
146+
self, client: TestClient, user: User, mocker: MockerFixture
147+
):
148+
mocker.patch("apps.authentication.api.auth.log")
149+
refresh_token = AuthenticationService.create_refresh_token(
150+
{
151+
"sub": str(user.id),
152+
"jti": str(uuid.uuid4()),
153+
"client": "admin",
154+
}
155+
)
156+
response = await client.post(url=self.refresh_access_token_url, data={"refresh_token": refresh_token})
157+
assert response.status_code == http.HTTPStatus.OK
158+
result = response.json()["result"]
159+
assert result["refreshToken"] == refresh_token
160+
access_payload = jwt.decode(
161+
result["accessToken"],
162+
settings.authentication.access_token.secret_key,
163+
algorithms=[settings.authentication.algorithm],
164+
)
165+
assert access_payload["client"] == "admin"
166+
167+
async def test_refresh_access_token__legacy_token_without_client_claim(
168+
self, client: TestClient, user: User, mocker: MockerFixture
169+
):
170+
mocker.patch("apps.authentication.api.auth.log")
171+
refresh_token = AuthenticationService.create_refresh_token(
172+
{
173+
"sub": str(user.id),
174+
"jti": str(uuid.uuid4()),
175+
}
176+
)
177+
response = await client.post(url=self.refresh_access_token_url, data={"refresh_token": refresh_token})
178+
assert response.status_code == http.HTTPStatus.OK
179+
access_payload = jwt.decode(
180+
response.json()["result"]["accessToken"],
181+
settings.authentication.access_token.secret_key,
182+
algorithms=[settings.authentication.algorithm],
183+
)
184+
assert "client" not in access_payload
185+
186+
async def test_refresh_token_key_transition__preserves_client_claim(
187+
self, client: TestClient, user: User, mocker: MockerFixture
188+
):
189+
token_key = settings.authentication.refresh_token.secret_key
190+
refresh_token = AuthenticationService.create_refresh_token(
191+
{
192+
"sub": str(user.id),
193+
"jti": str(uuid.uuid4()),
194+
"client": "web",
195+
}
196+
)
197+
new_token_key = "new token key"
198+
transition_expire_date = datetime.datetime.now(datetime.timezone.utc).date() + datetime.timedelta(days=1)
199+
200+
with mock.patch("config.settings.authentication.refresh_token") as token_settings_mock:
201+
token_settings_mock.secret_key = new_token_key
202+
token_settings_mock.transition_key = token_key
203+
token_settings_mock.transition_expire_date = transition_expire_date
204+
token_settings_mock.expiration = 540
205+
206+
_status_code, new_refresh_token = await self._request_refresh_token(client, refresh_token)
207+
assert _status_code == http.HTTPStatus.OK
208+
assert new_refresh_token
209+
assert new_refresh_token != refresh_token
210+
refresh_payload = jwt.decode(
211+
new_refresh_token,
212+
new_token_key,
213+
algorithms=[settings.authentication.algorithm],
214+
)
215+
assert refresh_payload["client"] == "web"
216+
145217
async def test_login_and_logout_device(self, client: TestClient, user: User):
146218
device_id = str(uuid.uuid4())
147219

0 commit comments

Comments
 (0)