Skip to content

Commit bc7eefa

Browse files
authored
replace 403 with 401 status code (#302)
1 parent d6ed009 commit bc7eefa

File tree

7 files changed

+56
-28
lines changed

7 files changed

+56
-28
lines changed

docs/source/jwt/middleware.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Setup
1111
-----
1212

1313
``JWTMiddleware`` wraps an ASGI app, and ensures a valid token is passed in the header.
14-
Otherwise a 403 error is returned. If the token is valid, the corresponding
14+
Otherwise a 401 error is returned. If the token is valid, the corresponding
1515
``user_id`` is added to the ASGI ``scope``.
1616

1717
blacklist

piccolo_api/jwt_auth/middleware.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import jwt
77
from piccolo.apps.user.tables import BaseUser
88
from starlette.exceptions import HTTPException
9+
from starlette.status import HTTP_401_UNAUTHORIZED
910
from starlette.types import ASGIApp
1011

1112

@@ -126,7 +127,7 @@ async def get_user(
126127
async def __call__(self, scope, receive, send):
127128
"""
128129
Add the user_id to the scope if a JWT token is available, and the user
129-
is recognised, otherwise raise a 403 HTTP error.
130+
is recognised, otherwise raise a 401 HTTP error.
130131
"""
131132
allow_unauthenticated = self.allow_unauthenticated
132133

@@ -142,7 +143,10 @@ async def __call__(self, scope, receive, send):
142143
)
143144
return
144145
else:
145-
raise HTTPException(status_code=403, detail=error)
146+
raise HTTPException(
147+
status_code=HTTP_401_UNAUTHORIZED,
148+
detail=error,
149+
)
146150

147151
if await self.blacklist.in_blacklist(token):
148152
error = JWTError.token_revoked.value
@@ -154,7 +158,10 @@ async def __call__(self, scope, receive, send):
154158
)
155159
return
156160
else:
157-
raise HTTPException(status_code=403, detail=error)
161+
raise HTTPException(
162+
status_code=HTTP_401_UNAUTHORIZED,
163+
detail=error,
164+
)
158165

159166
try:
160167
token_dict = jwt.decode(token, self.secret, algorithms=["HS256"])
@@ -168,7 +175,10 @@ async def __call__(self, scope, receive, send):
168175
)
169176
return
170177
else:
171-
raise HTTPException(status_code=403, detail=error)
178+
raise HTTPException(
179+
status_code=HTTP_401_UNAUTHORIZED,
180+
detail=error,
181+
)
172182
except jwt.exceptions.InvalidSignatureError:
173183
error = JWTError.token_invalid.value
174184
if allow_unauthenticated:
@@ -179,7 +189,10 @@ async def __call__(self, scope, receive, send):
179189
)
180190
return
181191
else:
182-
raise HTTPException(status_code=403, detail=error)
192+
raise HTTPException(
193+
status_code=HTTP_401_UNAUTHORIZED,
194+
detail=error,
195+
)
183196

184197
user = await self.get_user(token_dict)
185198
if user is None:
@@ -192,7 +205,10 @@ async def __call__(self, scope, receive, send):
192205
)
193206
return
194207
else:
195-
raise HTTPException(status_code=403, detail=error)
208+
raise HTTPException(
209+
status_code=HTTP_401_UNAUTHORIZED,
210+
detail=error,
211+
)
196212

197213
await self.asgi(
198214
extend_scope(scope, {"user_id": user.id}), receive, send

piccolo_api/mfa/endpoints.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from starlette.endpoints import HTTPEndpoint
99
from starlette.requests import Request
1010
from starlette.responses import HTMLResponse, JSONResponse
11+
from starlette.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED
1112

1213
from piccolo_api.mfa.provider import MFAProvider
1314
from piccolo_api.shared.auth.styles import Styles
@@ -64,7 +65,7 @@ def _render_cancel_template(
6465
template = environment.get_template("mfa_cancel.html")
6566

6667
return HTMLResponse(
67-
status_code=400,
68+
status_code=HTTP_400_BAD_REQUEST,
6869
content=template.render(
6970
styles=self._styles,
7071
csrftoken=request.scope.get("csrftoken"),
@@ -110,7 +111,7 @@ async def post(self, request: Request):
110111
):
111112
return self._render_register_template(
112113
request=request,
113-
status_code=403,
114+
status_code=HTTP_401_UNAUTHORIZED,
114115
extra_context={"error": "Incorrect password"},
115116
)
116117

piccolo_api/session_auth/endpoints.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
PlainTextResponse,
1818
RedirectResponse,
1919
)
20-
from starlette.status import HTTP_303_SEE_OTHER
20+
from starlette.status import HTTP_303_SEE_OTHER, HTTP_401_UNAUTHORIZED
2121

2222
from piccolo_api.mfa.provider import MFAProvider
2323
from piccolo_api.session_auth.tables import SessionsBase
@@ -92,7 +92,8 @@ async def post(self, request: Request) -> Response:
9292
cookie = request.cookies.get(self._cookie_name, None)
9393
if not cookie:
9494
raise HTTPException(
95-
status_code=401, detail="The session cookie wasn't found."
95+
status_code=HTTP_401_UNAUTHORIZED,
96+
detail="The session cookie wasn't found.",
9697
)
9798
await self._session_table.remove_session(token=cookie)
9899

@@ -204,11 +205,14 @@ def _get_error_response(
204205
) -> Response:
205206
if response_format == "html":
206207
return self._render_template(
207-
request, template_context={"error": error}, status_code=401
208+
request,
209+
template_context={"error": error},
210+
status_code=HTTP_401_UNAUTHORIZED,
208211
)
209212
else:
210213
return PlainTextResponse(
211-
status_code=401, content=f"Login failed: {error}"
214+
status_code=HTTP_401_UNAUTHORIZED,
215+
content=f"Login failed: {error}",
212216
)
213217

214218
async def get(self, request: Request) -> HTMLResponse:
@@ -261,7 +265,8 @@ async def post(self, request: Request) -> Response:
261265
)
262266
else:
263267
raise HTTPException(
264-
status_code=401, detail=validate_response
268+
status_code=HTTP_401_UNAUTHORIZED,
269+
detail=validate_response,
265270
)
266271

267272
# Attempt login
@@ -314,7 +319,8 @@ async def post(self, request: Request) -> Response:
314319
)
315320
else:
316321
raise HTTPException(
317-
status_code=401, detail=message
322+
status_code=HTTP_401_UNAUTHORIZED,
323+
detail=message,
318324
)
319325

320326
# Work out which MFA provider to use:
@@ -325,7 +331,7 @@ async def post(self, request: Request) -> Response:
325331

326332
if mfa_provider_name is None:
327333
raise HTTPException(
328-
status_code=401,
334+
status_code=HTTP_401_UNAUTHORIZED,
329335
detail="MFA provider must be specified",
330336
)
331337

@@ -337,13 +343,13 @@ async def post(self, request: Request) -> Response:
337343

338344
if len(filtered_mfa_providers) == 0:
339345
raise HTTPException(
340-
status_code=401,
346+
status_code=HTTP_401_UNAUTHORIZED,
341347
detail="MFA provider not recognised.",
342348
)
343349

344350
if len(filtered_mfa_providers) > 1:
345351
raise HTTPException(
346-
status_code=401,
352+
status_code=HTTP_401_UNAUTHORIZED,
347353
detail=(
348354
"Multiple matching MFA providers found."
349355
),
@@ -368,7 +374,7 @@ async def post(self, request: Request) -> Response:
368374
)
369375
else:
370376
raise HTTPException(
371-
status_code=401,
377+
status_code=HTTP_401_UNAUTHORIZED,
372378
detail="MFA failed",
373379
)
374380

@@ -404,7 +410,9 @@ async def post(self, request: Request) -> Response:
404410
},
405411
)
406412
else:
407-
raise HTTPException(status_code=401, detail="Login failed")
413+
raise HTTPException(
414+
status_code=HTTP_401_UNAUTHORIZED, detail="Login failed"
415+
)
408416

409417
now = datetime.now()
410418
expiry_date = now + self._session_expiry

piccolo_api/shared/middleware/junction.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from starlette.exceptions import HTTPException
22
from starlette.routing import Router
3+
from starlette.status import HTTP_404_NOT_FOUND
34
from starlette.types import Receive, Scope, Send
45

56

@@ -22,4 +23,4 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send):
2223
else:
2324
return
2425

25-
raise HTTPException(status_code=404)
26+
raise HTTPException(status_code=HTTP_404_NOT_FOUND)

piccolo_api/token_auth/endpoints.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from starlette.endpoints import HTTPEndpoint
88
from starlette.requests import Request
99
from starlette.responses import JSONResponse, Response
10+
from starlette.status import HTTP_401_UNAUTHORIZED
1011

1112
from .tables import TokenAuth
1213

@@ -61,11 +62,12 @@ async def post(self, request: Request) -> Response:
6162
else:
6263
return Response(
6364
content="The credentials were incorrect",
64-
status_code=401,
65+
status_code=HTTP_401_UNAUTHORIZED,
6566
)
6667
else:
6768
return Response(
68-
content="No credentials were found.", status_code=401
69+
content="No credentials were found.",
70+
status_code=HTTP_401_UNAUTHORIZED,
6971
)
7072

7173

tests/jwt_auth/test_jwt_middleware.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_empty_token(self):
4646
with self.assertRaises(HTTPException):
4747
response = client.get("/")
4848

49-
self.assertEqual(response.status_code, 403)
49+
self.assertEqual(response.status_code, 401)
5050
self.assertEqual(
5151
response.json()["detail"], JWTError.token_not_found.value
5252
)
@@ -103,7 +103,7 @@ def test_expired_token(self):
103103
with self.assertRaises(HTTPException):
104104
response = client.get("/", headers=headers)
105105

106-
self.assertEqual(response.status_code, 403)
106+
self.assertEqual(response.status_code, 401)
107107
self.assertEqual(
108108
response.json()["detail"], JWTError.token_expired.value
109109
)
@@ -134,7 +134,7 @@ def test_wrong_secret(self):
134134
with self.assertRaises(HTTPException):
135135
response = client.get("/", headers=headers)
136136

137-
self.assertEqual(response.status_code, 403)
137+
self.assertEqual(response.status_code, 401)
138138
self.assertEqual(
139139
response.json()["detail"], JWTError.token_invalid.value
140140
)
@@ -165,7 +165,7 @@ def test_missing_expiry(self):
165165
with self.assertRaises(HTTPException):
166166
response = client.get("/", headers=headers)
167167

168-
self.assertEqual(response.status_code, 403)
168+
self.assertEqual(response.status_code, 401)
169169
self.assertEqual(
170170
response.json()["detail"], JWTError.token_expired.value
171171
)
@@ -188,7 +188,7 @@ def test_token_without_user_id(self):
188188
with self.assertRaises(HTTPException):
189189
response = client.get("/", headers=headers)
190190

191-
self.assertEqual(response.status_code, 403)
191+
self.assertEqual(response.status_code, 401)
192192
self.assertEqual(response.content, b"")
193193

194194
# allow_unauthenticated

0 commit comments

Comments
 (0)