Skip to content

Commit ad49f3e

Browse files
committed
feat(auth): add JWT signed authentication for OAuth application API
1 parent fd092b3 commit ad49f3e

File tree

4 files changed

+63
-4
lines changed

4 files changed

+63
-4
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""
2+
Custom authenticators for the Support V1 API.
3+
"""
4+
import time
5+
6+
import jwt
7+
from django.conf import settings
8+
from django.contrib.auth.models import AnonymousUser
9+
from jwt import ExpiredSignatureError, InvalidTokenError
10+
from rest_framework import authentication
11+
from rest_framework.authentication import get_authorization_header
12+
13+
14+
class JWTsignedOauthAppAuthentication(authentication.BaseAuthentication):
15+
"""
16+
Authentication class to verify JWTs signed by trusted services.
17+
Allows authentication for the OauthApplicationAPIView in Open edX.
18+
"""
19+
20+
def authenticate(self, request):
21+
"""
22+
Extracts the JWT token from the Authorization header and verifies it.
23+
If authentication fails, it does NOT raise an exception to allow
24+
other authentication classes to attempt authentication.
25+
"""
26+
auth = get_authorization_header(request).split()
27+
28+
if not auth or auth[0].lower() != b'bearer':
29+
return None
30+
31+
if len(auth) != 2:
32+
return None
33+
34+
token = auth[1]
35+
return self.authenticate_token(token)
36+
37+
def authenticate_token(self, token):
38+
"""
39+
Attempts to authenticate the JWT token. If verification fails,
40+
returns None instead of raising an exception, allowing other authentication
41+
classes to handle authentication.
42+
"""
43+
try:
44+
decoded_payload = jwt.decode(
45+
token,
46+
settings.EOX_CORE_JWT_SIGNED_OAUTH_APP_PUBLIC_KEY,
47+
algorithms=["RS256"]
48+
)
49+
50+
if decoded_payload["exp"] < time.time():
51+
return None
52+
53+
return (AnonymousUser(), None)
54+
55+
except (ExpiredSignatureError, InvalidTokenError):
56+
return None

eox_core/api/support/v1/permissions.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
"""
44
Custom Support API permissions module
55
"""
6+
from django.conf import settings
7+
from django.contrib.auth.models import AnonymousUser
68
from rest_framework import exceptions, permissions
9+
from rest_framework.authentication import BaseAuthentication
10+
from rest_framework.exceptions import AuthenticationFailed
711

812

913
class EoxCoreSupportAPIPermission(permissions.BasePermission):
@@ -17,7 +21,7 @@ def has_permission(self, request, view):
1721
For now, to grant access only checks if the requesting user:
1822
1) is_staff
1923
"""
20-
if request.user.is_staff:
24+
if request.user is None or isinstance(request.user, AnonymousUser) or request.user.is_staff:
2125
return True
2226

2327
# To prevent leaking important information we return the most basic message.

eox_core/api/support/v1/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from rest_framework.response import Response
2121
from rest_framework.views import APIView
2222

23+
from eox_core.api.support.v1.authentication import JWTsignedOauthAppAuthentication
2324
from eox_core.api.support.v1.permissions import EoxCoreSupportAPIPermission
2425
from eox_core.api.support.v1.serializers import (
2526
OauthApplicationSerializer,
@@ -154,7 +155,7 @@ class OauthApplicationAPIView(UserQueryMixin, APIView):
154155
django_oauth_toolkit Application object.
155156
"""
156157

157-
authentication_classes = (BearerAuthentication, SessionAuthentication, JwtAuthentication)
158+
authentication_classes = (JWTsignedOauthAppAuthentication, BearerAuthentication, SessionAuthentication, JwtAuthentication)
158159
permission_classes = (EoxCoreSupportAPIPermission,)
159160
renderer_classes = (JSONRenderer, BrowsableAPIRenderer)
160161

@@ -208,7 +209,6 @@ def post(self, request, *args, **kwargs): # pylint: disable=too-many-locals
208209
owner user for the application.
209210
"""
210211
message = "Could not get or create edxapp User"
211-
212212
serializer = OauthApplicationSerializer(data=request.data)
213213
serializer.is_valid(raise_exception=True)
214214

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
from setuptools import setup
1010

11-
1211
with open("README.rst", "r") as fh:
1312
README = fh.read()
1413

0 commit comments

Comments
 (0)