104 lines
3.7 KiB
Python
104 lines
3.7 KiB
Python
"""
|
|
OAuth2 Bearer Token Authentication Middleware for CMS.
|
|
|
|
The Studio MFE (course-authoring MFE) authenticates API calls to CMS using
|
|
OAuth2 Bearer tokens obtained from the LMS during login.
|
|
|
|
When the user logs in via the authn MFE:
|
|
1. LMS sets a Django session cookie
|
|
2. LMS returns an OAuth2 access token
|
|
3. The MFE stores the access token in localStorage
|
|
|
|
On every API request to CMS, the MFE's Axios sends:
|
|
Authorization: Bearer <access_token>
|
|
|
|
This middleware intercepts those requests, validates the Bearer token
|
|
against the LMS userinfo endpoint, and attaches the Django user to the request.
|
|
"""
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class CMSOAuth2BearerAuthMiddleware:
|
|
"""
|
|
Middleware that authenticates CMS API requests using OAuth2 Bearer tokens.
|
|
|
|
The Studio MFE (course-authoring MFE) stores an OAuth2 access token
|
|
obtained from LMS login in localStorage, and sends it as:
|
|
Authorization: Bearer <access_token>
|
|
on every API request to CMS.
|
|
|
|
This middleware validates the token against the LMS userinfo endpoint
|
|
and attaches the authenticated Django user to the request.
|
|
"""
|
|
|
|
def __init__(self, get_response):
|
|
self.get_response = get_response
|
|
self.lms_userinfo_url = "http://lms:8000/oauth2/user_info"
|
|
|
|
def __call__(self, request):
|
|
# Check for Bearer token in Authorization header
|
|
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
|
|
logger.debug(
|
|
"CMSOAuth2BearerAuth: path=%s auth_header=%r",
|
|
request.path, auth_header[:30] if auth_header else None
|
|
)
|
|
if auth_header.startswith('Bearer '):
|
|
token = auth_header[7:]
|
|
if token:
|
|
user = self._validate_token_get_user(token)
|
|
if user:
|
|
# Attach the user to the request
|
|
# This makes request.user available for Django's auth checks
|
|
request.user = user
|
|
# Cache the user to prevent re-evaluation
|
|
request._cached_user = user
|
|
logger.debug(
|
|
"CMSOAuth2BearerAuth: token validated for user=%s (id=%s)",
|
|
user.username, user.id
|
|
)
|
|
else:
|
|
logger.debug("OAuth2 Bearer token validation failed")
|
|
else:
|
|
# No Bearer token - let session auth handle it
|
|
pass
|
|
|
|
return self.get_response(request)
|
|
|
|
def _validate_token_get_user(self, token):
|
|
"""Call LMS userinfo endpoint and return Django User."""
|
|
import requests
|
|
try:
|
|
resp = requests.get(
|
|
self.lms_userinfo_url,
|
|
headers={'Authorization': f'Bearer {token}'},
|
|
timeout=5,
|
|
)
|
|
if resp.status_code != 200:
|
|
logger.debug(
|
|
"LMS userinfo returned %s for token validation",
|
|
resp.status_code
|
|
)
|
|
return None
|
|
|
|
user_info = resp.json()
|
|
username = user_info.get('username')
|
|
if not username:
|
|
logger.debug("No username in LMS userinfo response")
|
|
return None
|
|
|
|
# Get the Django User object
|
|
from django.contrib.auth import get_user_model
|
|
User = get_user_model()
|
|
try:
|
|
user = User.objects.get(username=username)
|
|
logger.debug("Found Django user: %s (id=%s)", username, user.id)
|
|
return user
|
|
except User.DoesNotExist:
|
|
logger.debug("Django user not found: %s", username)
|
|
return None
|
|
except Exception as e:
|
|
logger.debug("Error validating OAuth2 token: %s", e)
|
|
return None
|