Skip to content

Commit 911da08

Browse files
fix: treat missing auth as unauthorized
1 parent 73d9c36 commit 911da08

5 files changed

Lines changed: 45 additions & 16 deletions

File tree

‎packages/dara-core/changelog.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
title: Changelog
33
---
44

5+
## NEXT
6+
7+
- Fixed missing auth session cookies being reported as bad requests instead of unauthorized session failures, so expired browser cookies redirect users back to login, and limited refresh-token retries to authentication failures.
8+
59
## 1.26.12
610

711
- Internal: Hardened JavaScript dependency versions and removed unused build and test tooling dependencies.

‎packages/dara-core/dara/core/auth/routes.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def _get_auth_token(
170170
if session_cookie_token is not None:
171171
return session_cookie_token
172172

173-
raise HTTPException(status_code=400, detail=BAD_REQUEST_ERROR(missing_message))
173+
raise HTTPException(status_code=401, detail=EXPIRED_TOKEN_ERROR)
174174

175175

176176
def _set_session_token_cookie(response: Response, token: str):

‎packages/dara-core/js/api/http.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ export async function request(url: string | URL, ...options: RequestInit[]): Pro
9797
...other,
9898
});
9999

100-
// in case of an auth error, attempt to refresh the token and retry the request
101-
if (response.status === 401 || response.status === 403) {
100+
// in case of an authentication error, attempt to refresh the token and retry the request
101+
if (response.status === 401) {
102102
try {
103103
// Ensure only one tab refreshes at a time; others wait and reuse the refreshed token.
104104
await runSessionRefresh(async () => {

‎packages/dara-core/tests/js/http.spec.tsx‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,18 @@ describe('HTTP Utils', () => {
8383

8484
afterAll(() => server.close());
8585

86-
it('attempts to refresh the token if 402 or 403 occurs, failing if no refresh token is present', async () => {
86+
it('attempts to refresh the token on 401, failing if no refresh token is present', async () => {
8787
const res = await request('/error-401', { method: 'GET' });
8888
expect(res.status).toBe(401);
8989
expect(refreshAttempted).toHaveBeenCalledTimes(1);
90+
});
91+
92+
it('does not attempt to refresh the token on 403', async () => {
93+
document.cookie = `${REFRESH_TOKEN_NAME}=${REFRESH_TOKEN}; `;
9094

91-
const res2 = await request('/error-403', { method: 'GET' });
92-
expect(res2.status).toBe(403);
93-
expect(refreshAttempted).toHaveBeenCalledTimes(2);
95+
const res = await request('/error-403', { method: 'GET' });
96+
expect(res.status).toBe(403);
97+
expect(refreshAttempted).not.toHaveBeenCalled();
9498
});
9599

96100
it('refreshes the token and retries the request', async () => {
@@ -249,7 +253,7 @@ describe('HTTP Utils', () => {
249253
expect(requested401).toHaveBeenCalledTimes(1);
250254

251255
// make another request while the refresh is still in progress
252-
const res2Promise = request('/error-403');
256+
const res2Promise = request('/error-401');
253257

254258
resolve();
255259
delay = null;
@@ -265,9 +269,7 @@ describe('HTTP Utils', () => {
265269
expect(await res2.json()).toEqual({ success: true });
266270

267271
expect(refreshAttempted).toHaveBeenCalledTimes(1);
268-
// first request was made twice
269-
expect(requested401).toHaveBeenCalledTimes(2);
270-
// the second request should not have made a request until it got the new token
271-
expect(requested403).toHaveBeenCalledTimes(1);
272+
// first request was made twice; the second request waited and only sent after refresh completed
273+
expect(requested401).toHaveBeenCalledTimes(3);
272274
});
273275
});

‎packages/dara-core/tests/python/test_auth.py‎

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@
88
from fastapi import Request
99

1010
from dara.core.auth.basic import BasicAuthConfig, MultiBasicAuthConfig
11-
from dara.core.auth.definitions import ID_TOKEN, JWT_ALGO, SESSION_ID, SESSION_TOKEN_COOKIE_NAME, TokenData
11+
from dara.core.auth.definitions import (
12+
EXPIRED_TOKEN_ERROR,
13+
ID_TOKEN,
14+
JWT_ALGO,
15+
SESSION_ID,
16+
SESSION_TOKEN_COOKIE_NAME,
17+
TokenData,
18+
)
1219
from dara.core.auth.utils import token_refresh_cache
1320
from dara.core.configuration import ConfigurationBuilder
1421
from dara.core.http import get
@@ -87,7 +94,8 @@ def handle():
8794

8895
# Test no auth
8996
response = await client.get('/api/test-ext/test')
90-
assert response.status_code == 400
97+
assert response.status_code == 401
98+
assert response.json()['detail'] == EXPIRED_TOKEN_ERROR
9199

92100
# Test wrong scheme
93101
response = await client.get('/api/test-ext/test', headers={'Authorization': 'Basic user:pw'})
@@ -150,6 +158,21 @@ async def test_verify_session_invalid_cookie_clears_auth_cookies_without_refresh
150158
assert any(cookie.startswith(f'{SESSION_TOKEN_COOKIE_NAME}="";') for cookie in cleared_cookies)
151159

152160

161+
async def test_verify_session_missing_auth_returns_unauthorized():
162+
"""Check that missing auth credentials are treated as an auth failure, not a bad request."""
163+
164+
config = ConfigurationBuilder()
165+
config.add_auth(BasicAuthConfig('test', 'test'))
166+
167+
app = _start_application(config._to_configuration())
168+
169+
async with AsyncClient(app) as client:
170+
response = await client.post('/api/auth/verify-session')
171+
172+
assert response.status_code == 401
173+
assert response.json()['detail'] == EXPIRED_TOKEN_ERROR
174+
175+
153176
async def test_authorization_header_injected_from_session_cookie():
154177
"""Check that a missing Authorization header is synthesized from the session cookie"""
155178

@@ -355,8 +378,8 @@ async def test_stale_session_cookie_recovers_after_refresh_attempt():
355378

356379
# Cookie jar should no longer contain stale auth cookies after refresh response.
357380
followup_verify_response = await client.post('/api/auth/verify-session')
358-
assert followup_verify_response.status_code == 400
359-
assert followup_verify_response.json()['detail']['message'] == 'No auth credentials passed'
381+
assert followup_verify_response.status_code == 401
382+
assert followup_verify_response.json()['detail'] == EXPIRED_TOKEN_ERROR
360383

361384

362385
async def test_refresh_token_unsupported():

0 commit comments

Comments
 (0)