So I have this custom authenticator created and I have over 30 endpoints. For all, but 3 endpoints it requires authentication. So I'm pretty much adding @custom_authenticator
to every function or @method_decorator(custom_authenticator)
in the case of APIView
classes. Is there a way I can automatically add this to endpoints and add a decorator that turns off authentication for specific endpoint functions? For example
@donotauth
def endpoint(request)
then endpoint()
won't run the authenticator first. The solution should ideally work with the custom authenticator below
custom authenticator
def cognito_authenticator(view_func=None):
if view_func is None:
return partial(cognito_authenticator)
@wraps(view_func)
def wrapped_view(request, *args, **kwargs):
# Check the cognito token from the request.
auth = request.headers.get("Authorization", None)
if not auth:
return Response(dict(error='Authorization header expected'), status=status.HTTP_401_UNAUTHORIZED)
parts = auth.split()
if parts[0].lower() != "bearer":
return Response(dict(error='Authorization header must start with bearer'),
status=status.HTTP_401_UNAUTHORIZED)
elif len(parts) == 1:
return Response(dict(error='Token not found'), status=status.HTTP_401_UNAUTHORIZED)
elif len(parts) > 2:
return Response(dict(error='Authorization header must be Bearer token'),
status=status.HTTP_401_UNAUTHORIZED)
token = parts[1]
try:
res = decode_cognito_jwt(token)
expiration = datetime.utcfromtimestamp(res['exp'])
current_utc = datetime.utcnow()
if current_utc > expiration:
return Response(dict(error=f'current time:{current_utc} is after expiration:{expiration}',
user_msg='Please login again'), status=status.HTTP_400_BAD_REQUEST)
except Exception:
# Fail if invalid
return Response(dict(error="Invalid JWT"),
status=status.HTTP_401_UNAUTHORIZED) # Or HttpResponseForbidden()
else:
# Proceed with the view if valid
return view_func(request, *args, **kwargs)
return wrapped_view
Solution 1 using Middleware:
I tried adding middleware, but it throws an error for anything with @api_view
decorator on it. The error I get is AssertionError: .accepted_renderer not set on Response.
How do I setup my custom authentication on every endpoint regardless of if it has @api_view
decorator or is APIView
. The end goal should be to automatically add the above cognito_authenticator
to any endpoint and a way to specify when not to use the authenticator (probably a functiond decorator)
View.py
@api_view(['GET'])
@swagger_auto_schema(
operation_description="Get <count> most recent posts by category"
)
def get_most_recent_posts_by_category(request, category, count):
return Response(status=status.HTTP_200_OK)
Middleware
from datetime import datetime
from rest_framework import status
from rest_framework.response import Response
from cheers.core.api.jwt_helpers import decode_cognito_jwt
class CognitoMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_view(self, request, view_func, view_args, view_kwargs):
auth = request.headers.get("Authorization", None)
if not auth:
return Response(dict(error='Authorization header expected'), status=status.HTTP_401_UNAUTHORIZED)
parts = auth.split()
if parts[0].lower() != "bearer":
return Response(dict(error='Authorization header must start with bearer'),
status=status.HTTP_401_UNAUTHORIZED)
elif len(parts) == 1:
return Response(dict(error='Token not found'), status=status.HTTP_401_UNAUTHORIZED)
elif len(parts) > 2:
return Response(dict(error='Authorization header must be Bearer token'),
status=status.HTTP_401_UNAUTHORIZED)
token = parts[1]
try:
res = decode_cognito_jwt(token)
expiration = datetime.utcfromtimestamp(res['exp'])
current_utc = datetime.utcnow()
if current_utc > expiration:
return Response(dict(error=f'current time:{current_utc} is after expiration:{expiration}',
user_msg='Please login again'), status=status.HTTP_400_BAD_REQUEST)
except Exception:
# Fail if invalid
return Response(dict(error="Invalid JWT"),
status=status.HTTP_401_UNAUTHORIZED) # Or HttpResponseForbidden()
else:
# Proceed with the view if valid
return None
settings.py
MIDDLEWARE = [
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'cheers.middleware.CognitoMiddleware.CognitoMiddleware'
]
Solution 2 using authenticator
authenticator.py
class CognitoAuthentication(BaseAuthentication):
def authenticate(self, request):
auth = request.headers.get("Authorization", None)
if not auth:
return Response(dict(error='Authorization header expected'), status=status.HTTP_401_UNAUTHORIZED)
parts = auth.split()
if parts[0].lower() != "bearer":
return Response(dict(error='Authorization header must start with bearer'),
status=status.HTTP_401_UNAUTHORIZED)
elif len(parts) == 1:
return Response(dict(error='Token not found'), status=status.HTTP_401_UNAUTHORIZED)
elif len(parts) > 2:
return Response(dict(error='Authorization header must be Bearer token'),
status=status.HTTP_401_UNAUTHORIZED)
token = parts[1]
try:
res = decode_cognito_jwt(token)
expiration = datetime.utcfromtimestamp(res['exp'])
current_utc = datetime.utcnow()
if current_utc > expiration:
return Response(dict(error=f'current time:{current_utc} is after expiration:{expiration}',
user_msg='Please login again'), status=status.HTTP_400_BAD_REQUEST)
except Exception:
# Fail if invalid
return Response(dict(error="Invalid JWT"),
status=status.HTTP_401_UNAUTHORIZED) # Or HttpResponseForbidden()
else:
# Proceed with the view if valid
return AnonymousUser(), None
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'cheers.utils.authenticator.CognitoAuthentication',
),
}
but on an APIView
post function it gives the error django.template.response.ContentNotRenderedError: The response content must be rendered before it can be iterated over.