Skip to content

Commit 8f2aab9

Browse files
committed
Add JWT Token authenticator
1 parent ee83f66 commit 8f2aab9

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

src/argus/auth/authentication.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
from datetime import timedelta
2+
import urllib.request, json
3+
4+
import jwt
25

36
from django.conf import settings
47
from django.utils import timezone
5-
from rest_framework.authentication import TokenAuthentication
8+
from rest_framework.authentication import TokenAuthentication, BaseAuthentication
69
from rest_framework.exceptions import AuthenticationFailed
710

11+
from .models import User
12+
813

914
class ExpiringTokenAuthentication(TokenAuthentication):
1015
EXPIRATION_DURATION = timedelta(days=settings.AUTH_TOKEN_EXPIRES_AFTER_DAYS)
@@ -17,3 +22,55 @@ def authenticate_credentials(self, key):
1722
raise AuthenticationFailed("Token has expired.")
1823

1924
return user, token
25+
26+
27+
class JWTAuthentication(BaseAuthentication):
28+
def authenticate(self, request):
29+
try:
30+
raw_token = self.get_raw_jwt_token(request)
31+
except ValueError:
32+
return None
33+
try:
34+
validated_token = jwt.decode(
35+
jwt=raw_token,
36+
algorithms=["RS256", "RS384", "RS512"],
37+
key=self.get_public_key(),
38+
options={
39+
"require": [
40+
"exp",
41+
"nbf",
42+
"aud",
43+
"iss",
44+
"sub",
45+
]
46+
},
47+
audience=settings.JWT_AUDIENCE,
48+
issuer=settings.JWT_ISSUER,
49+
)
50+
except jwt.exceptions.PyJWTError as e:
51+
raise AuthenticationFailed(f"Error validating JWT token: {e}")
52+
username = validated_token["sub"]
53+
user = User.objects.get(username=username)
54+
if not user:
55+
raise AuthenticationFailed(f"No user found for username {username}")
56+
57+
return user, validated_token
58+
59+
def get_public_key(self):
60+
with urllib.request.urlopen(settings.JWK_ENDPOINT) as url:
61+
jwk_data = json.load(url)
62+
public_key = jwk_data["keys"][0]["n"]
63+
return public_key
64+
65+
def get_raw_jwt_token(self, request):
66+
"""Raises ValueError if a jwt token could not be found"""
67+
auth_header = request.META.get("HTTP_AUTHORIZATION", None)
68+
if not auth_header:
69+
raise ValueError("No Authorization header found")
70+
try:
71+
scheme, token = auth_header.split()
72+
except ValueError as e:
73+
raise ValueError(f"Failed to parse Authorization header: {e}")
74+
if scheme.lower() != "bearer":
75+
raise ValueError(f"Invalid Authorization scheme: {scheme}")
76+
return token

src/argus/site/settings/base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@
187187
"argus.auth.authentication.ExpiringTokenAuthentication",
188188
# For BrowsableAPIRenderer
189189
"rest_framework.authentication.SessionAuthentication",
190+
"argus.auth.authentication.JWTAuthentication",
190191
),
191192
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
192193
"DEFAULT_RENDERER_CLASSES": (
@@ -301,3 +302,7 @@
301302
#
302303
# SOCIAL_AUTH_DATAPORTEN_FEIDE_KEY = SOCIAL_AUTH_DATAPORTEN_KEY
303304
# SOCIAL_AUTH_DATAPORTEN_FEIDE_SECRET = SOCIAL_AUTH_DATAPORTEN_SECRET
305+
306+
JWK_ENDPOINT = get_str_env("JWK_ENDPOINT")
307+
JWT_ISSUER = get_str_env("JWT_ISSUER")
308+
JWT_AUDIENCE = get_str_env("JWT_AUDIENCE")

0 commit comments

Comments
 (0)