47๐
How to implement a custom authentication scheme in DRF?
To implement a custom authentication scheme, we need to subclass the DRFโs BaseAuthentication
class and override the .authenticate(self, request)
method.
The method should return a two-tuple of (user, auth)
if authentication succeeds, or None
otherwise. In some circumstances, we may raise an AuthenticationFailed
exception from the .authenticate()
method.
Example (from DRF docs):
Lets say we want to authenticate any incoming request as the user given by the username
in a custom request header named 'X_USERNAME'
.
Step-1: Create the Custom authentication class
To do that, we will create an authentication.py
file in my_app
.
# my_app/authentication.py
from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions
class ExampleAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
username = request.META.get('X_USERNAME') # get the username request header
if not username: # no username passed in request headers
return None # authentication did not succeed
try:
user = User.objects.get(username=username) # get the user
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user') # raise exception if user does not exist
return (user, None) # authentication successful
Step-2: Specify the custom authentication class
After creating the custom authentication class, we need to define this authentication class in our DRF settings. Doing this, all the requests will be authenticated based on this authentication scheme.
'DEFAULT_AUTHENTICATION_CLASSES': (
'my_app.authentication.ExampleAuthentication', # custom authentication class
...
),
Note: If you want to use this custom authentication class on per-view basis or per-viewset basis and not on global level, you can define this authentication class explicitly in your views.
class MyView(APIView):
authentication_classes = [ExampleAuthentication,] # specify this authentication class in your view
...
- [Django]-Django REST Framework how to specify error code when raising validation error in serializer
16๐
Below is a simple example that can be used to achieve a custom authentication. To access the endpoint you have to pass the username&password on the POST data.
urls.py
urlpatterns = [
url(r'^stuff/', views.MyView.as_view()),
...
]
views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework import exceptions
from rest_framework import authentication
from django.contrib.auth import authenticate, get_user_model
from rest_framework.authentication import SessionAuthentication
class ExampleAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
# Get the username and password
username = request.data.get('username', None)
password = request.data.get('password', None)
if not username or not password:
raise exceptions.AuthenticationFailed(_('No credentials provided.'))
credentials = {
get_user_model().USERNAME_FIELD: username,
'password': password
}
user = authenticate(**credentials)
if user is None:
raise exceptions.AuthenticationFailed(_('Invalid username/password.'))
if not user.is_active:
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
return (user, None) # authentication successful
class MyView(APIView):
authentication_classes = (SessionAuthentication, ExampleAuthentication,)
permission_classes = (IsAuthenticated,)
def post(self, request, format=None):
content = {
'user': unicode(request.user),
'auth': unicode(request.auth), # None
}
return Response(content)
Curl
curl -v -X POST http://localhost:8000/stuff/ -d 'username=my_username&password=my_password'
- [Django]-Django: change the value of a field for all objects in a queryset
- [Django]-Django ModelChoiceField optgroup tag
- [Django]-Django: How to filter Users that belong to a specific group
2๐
I used the following way
from rest_framework_jwt.settings import api_settings
from rest_framework import status, generics
class UserLogin(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
email = request.data['email']
if email is None:
return Response({'error': 'Email not informed'}, status=status.HTTP_403_FORBIDDEN)
try:
user = User.objects.get(email=email)
if not user.check_password(request.data['password']):
return Response({'error': 'Email ou senha incorreto'}, status=status.HTTP_400_BAD_REQUEST)
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return Response({"token": token, "user":UserSessionSerializerAuth(user,
context={'request': request}).data}, status=status.HTTP_200_OK)
except User.DoesNotExist:
return Response({'error': 'User not found'}, status=status.HTTP_403_FORBIDDEN)
- [Django]-Django: Purpose of django.utils.functional.SimpleLazyObject?
- [Django]-How to handle per object permission in Django nowadays?
- [Django]-Django Template Language: Using a for loop with else
1๐
I faced similar situation where I had to implement a custom authentication class. Request coming to the rest endpoint were using basic auth
but the username werenโt a django user. Requests were authnticated against username and password configured in settings.py
. Hereโs my implementation:
โ Created custom authentication class
# myapp/api/auth.py
"""Custom authentication module"""
import base64
import binascii
from django.conf import settings
from django.utils.six import text_type
from django.utils.translation import ugettext_lazy as _
from rest_framework.authentication import BaseAuthentication
from rest_framework import HTTP_HEADER_ENCODING, exceptions
class CustomAuthentication(BaseAuthentication):
"""
Custom authentication class.
It will authenticate any incoming request
as the user given by the username in a
custom request header.
"""
def authenticate(self, request):
"""
Returns a `User` if a correct username and password have been supplied
using HTTP Basic authentication. Otherwise returns `None`.
"""
# Gets authorization from request header
# and checks different possibility of
# invalid header.
# ======================================
auth = self.get_authorization_header(request).split()
if not auth or auth[0].lower() != b"basic":
raise exceptions.AuthenticationFailed(_("Invalid header!"))
if len(auth) == 1:
msg = _("Invalid basic header. No credentials provided.")
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _(
"Invalid basic header. Credentials string should not contain spaces."
)
raise exceptions.AuthenticationFailed(msg)
try:
auth_parts = (
base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(":")
)
except (TypeError, UnicodeDecodeError, binascii.Error):
msg = _("Invalid basic header. Credentials not correctly base64 encoded.")
raise exceptions.AuthenticationFailed(msg)
# parses username and password.
userid, password = auth_parts[0], auth_parts[2]
if userid != settings.FERRATUM_CALLBACK_USERNAME:
msg = _("Invalid basic header. Username is incorrect!")
raise exceptions.AuthenticationFailed(msg)
if password != settings.FERRATUM_CALLBACK_PASSWORD:
msg = _("Invalid basic header. Password is incorrect!")
raise exceptions.AuthenticationFailed(msg)
# An user object is expected to be returned
# in case of successful authentication. Therefore
# a user object is returned with the give username
# in the header. This user doesn't exists in the
# django user User model.
# ===============================================
user = {
"username": userid,
"password": "",
"email": "",
"first_name": "",
"last_name": "",
"company": "",
}
return (user, None)
@staticmethod
def get_authorization_header(request):
"""
Return request's 'Authorization:' header, as a bytestring.
Hide some test client ickyness where the header can be unicode.
"""
auth = request.META.get("HTTP_AUTHORIZATION", b"")
if isinstance(auth, text_type):
# Work around django test client oddness
auth = auth.encode(HTTP_HEADER_ENCODING)
return auth
โ Added it in DEFAULT_AUTHENTICATION_CLASS
# myapp/settings.py
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
"mozilla_django_oidc.contrib.drf.OIDCAuthentication",
"rest_framework.authentication.BasicAuthentication",
"users.auth.SaveriumTokenAuthentication",
"api.auth.CustomAuthentication"
),
'DEFAULT_RENDERER_CLASSES': DEFAULT_RENDERER_CLASSES
}
โ Used the custom authentication in view
# myapp/api/view/api_view.py
from api.auth import CustomAuthentication
class UpdateStatus(APIView):
"""
It updates application status
"""
# Custom authentication scheme.
authentication_classes = [CustomAuthentication]
def post(self, *args, **kwargs):
"""
Callback comes as a POST request
with data in JSON format.
"""
data = self.request.data
...
- [Django]-Django โ show the length of a queryset in a template
- [Django]-How to override and extend basic Django admin templates?
- [Django]-Django: templatedoesnotexist gis/admin/openlayers.html
0๐
In the folder that holds your api files, create another file to hold your custom authentication class, such as authentication.py
. Then in your settings, under DEFAULT_AUTHENTICATION_CLASSES, point to your custom authentication class.
- [Django]-Object does not support item assignment error
- [Django]-Django: Why do some model fields clash with each other?
- [Django]-Add request.GET variable using django.shortcuts.redirect