User Authentication with Django REST Framework

 In Python, Tutorials

User Authentication is a simple concept, but when it comes to properly implementing it in Django, things can get complicated. Django offers an abundance of different authentication mechanisms: BasicAuthentication, TokenAuthentication, SessionAuthentication, and various ways to implement custom authentication mechanisms.

The introduction of Django REST Framework did great things for Django. However, it introduces another layer of confusion, and often leaves the developer asking “Which authentication mechanism should I use?”
If you’re building a web app backed by a Django REST Framework API, and that web app needs to handle user authentication, then you probably have the following requirements for user authentication:

  • Users must be able to authenticate with the API using a username and password.
  • Authentication with the API should return a token for future requests.
  • Authentication tokens should expire after a set amount of time.
  • Users should be able to refresh their token with an API request.
  • Users should be able to change their passwords.

These seem like simple requirements, but none of the built-in authentication mechanisms fully satisfy these needs, or lack the necessary amount of security. In this article, I’m going to show you what has become my favorite way of getting user authentication working in my web apps. It’s a great little solution for web apps that have to manage user sessions.

I’ll assume that you already have a working Django REST Framework project for this tutorial. If you don’t check out my series on starting a new Django REST Framework project.

Setting Up User Authentication and Registration

django-rest-auth is a great little package which encompasses almost everything you need to get user authentication up and running in your API. On it’s own, it fulfills a majority of the requirements I outlined above.

What Modifications Do We Need to Make?

To fulfill our needs, we’re going to also configure django-rest-auth’s optional registration app. This will allow us to easily register new user accounts without any custom code on our part.

By default, django-rest-auth uses standard Django tokens to allow users to authenticate. However, these tokens never change. So we’re also going to configure django-rest-auth to use django-rest-framework-jwt: a package providing a secure JWT implementation. These tokens can be configured to expire after a set amount of time, which is much more secure than the default token implementation.

Installation

To get started, let’s install the three packages we’ll be using.

pip install django-rest-auth
pip install djangorestframework-jwt
pip install django-allauth

Configure the Django Settings

Now that we have our packages installed, add the following apps to your settings file.

INSTALLED_APPS = (
    ...
    'rest_framework',
    'rest_auth',
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'rest_auth.registration',
)

At this point, the django-rest-auth tutorial would also tell you to add the rest_framework.authtoken app as well. However, since we’re going to use JWTs, we don’t need to do this.

To use JSON Web Tokens (JWTs), we’ll have to also add the following configuration settings:

import datetime

# Configure the JWTs to expire after 1 hour, and allow users to refresh near-expiration tokens
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(hours=1),
    'JWT_ALLOW_REFRESH': True,
}

# Make JWT Auth the default authentication mechanism for Django
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),
}

# Enables django-rest-auth to use JWT tokens instead of regular tokens.
REST_USE_JWT = True

To make the registration app work, we’ll also have to add the following setting, which is required for the django.contrib.sites dependency.

SITE_ID = 1

That’s all the setting changes that we’ll need to start adding our views.

Adding our Views

Django-rest-auth and django-rest-framework-jwt provide a few views for us that we can enable in our api.

Add the following to your urls.py  file to enable login, logout, and registration (and the rest of the django-rest-auth endpoints listed here).

urlpatterns = [
    ...
    url(r'^', include('rest_auth.urls')),
    url(r'^registration/', include('rest_auth.registration.urls'))
]

There’s still one more thing we’ll have to do if we want to allow users to refresh their tokens without logging in again. django-rest-framework-jwt has a view we can include which will do just this. Add the refresh_jwt_token view to your urls as shown below.

from rest_framework_jwt.views import refresh_jwt_token

urlpatterns = [
    ...
    url(r'^rest-auth/', include('rest_auth.urls')),
    url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
    ...
    url(r'^refresh-token/', refresh_jwt_token),
]

Now we’re done! Run python manage.py migrate to make all the necessary database changes, and then you will be able to start using these.

Testing our Views

Now that the heavy lifting is done, I could end this blog post. A lot of people probably would. I mean, I told you everything you need to know to get this working, right?

Wrong.

Now I’m going to show you how to actually use the API we just built. In my opinion, nothing is worse than knowing exactly how to configure something to work, and then not knowing how to use it.

When I’m doing this type of testing, I usually use an app called Postman. I’d recommend getting that, but you could also make these HTTP requests on the command line using a tool like cURL.

Below, I’ll give you the endpoint, method, and payload for each action we want to perform. You can plug this information into Postman or use it in an AJAX request in your code to use them.

Register

Method: POST
Endpoint: /rest-auth/registration/
Payload:

{
    "username": "USERNAME",
    "password1": "PASSWORD",
    "password2": "PASSWORD",
    "email": "OPTIONAL_EMAIL"
}

Login

Method: POST
Endpoint: /rest-auth/login/
Payload:

{
    "username": "USERNAME",
    "password": "PASSWORD"
}

Logout

Method: POST
Endpoint: /rest-auth/logout/
Headers: Authorization: JWT YOUR_TOKEN_HERE

Refresh Token

Method: POST
Endpoint: /refresh-token/
Payload:

{
    "token": "YOUR_OLD_TOKEN"
}

That’s it, use Postman, curl, or another tool to build requests based on the above descriptions and you’ll be all set!

Web app development is slowly trending towards having a separate UI and API.  With that, comes a different need for user authentication. I hope this tutorial helps you fulfill that need the best way possible.

Let me know if you have any questions or suggestions in the comments below! Stay tuned for a blog post on API key authentication, for those of you creating RESTful web services.

Showing 41 comments
  • Quentin
    Reply

    Correction for code line 2 of “Installation”. django-rest-framework-jwt is actually djangorestframework-jwt

    • Michael Washburn Jr.
      Reply

      Nice catch! Thanks!

    • William Fuentes
      Reply

      Hi first thaks for the tutorial, I have a problem with the confirmation email, when I test the api(http://localhost:8000/rest-auth/registration/) with postman I recive a confirmation email, but when I try to confirm the email using the link that I recive in the email I have an error like this

      “ImproperlyConfigured at /rest-auth/registration/account-confirm-email/Mw:1fIdXY:fJ8VjWwzFUGvdakFPA723w47MJo/
      TemplateResponseMixin requires either a definition of ‘template_name’ or an implementation of ‘get_template_names()'”

      I wonder if Could you help me with that

  • Tadeo Carrier
    Reply

    Hey Michael, thanks for this article!
    I’m looking for a way to integrate social authentication to my Django + DRF app and I’m a bit lost.
    I used Django allauth in the past and seems the most widely used app for social auth but it seems not to support ajax interaction out of the box. Do you have any experience or clue about how to tackle this problem?
    Thank you very much!

    • Michael Washburn Jr.
      Reply

      Hey there, django-rest-auth (the same package I used in this tutorial) uses django-allauth under the hood, and provides endpoints for social authentication. You can get it working by following this tutorial, and also doing the optional social auth configuration mentioned here. Then, you’ll be able to use django-rest-auth’s endpoints to authenticate with Facebook and Twitter (see here). If you need more social networks than that, you can include additional allauth provider apps in your INSTALLED_APPS (see here for all the possible providers). I hope that helps, thanks for reading!

      • Tadeo Carrier
        Reply

        Hey Michael, thank you so much for your soon answer. I’m going to follow the links and try to get it to work.
        You save me a lot of work and research, thanks again!
        (FWI: I found your article through the LinkedIn Django group)

  • Ickerday
    Reply

    Cheers for the solution Michael, I have two issues though.
    1. Your payloads contain some odd ” quotes, so I search-and-replaced them with standard ” quotes and it worked fine afterwards.
    2. I tried all the payloads and they seem to work fine except for the logout. Django returns 403 with a following message: { "detail": "CSRF Failed: CSRF cookie not set." }. How should I go about sending that cookie to Postman to be able to set it in the header as X-CSRFToken?

    • Michael Washburn Jr.
      Reply

      Hey there, thanks for catching those odd quotes.. I’m not sure how that happened! Regarding your second item, I believe this is because you still have SessionAuthentication enabled in your settings. Is that true? If so, try removing it and see if the CSRF error stops. Let me know if that works!

      • Ickerday
        Reply

        Yes, it worked! Thanks for the quick reply, Michael! Also, cheers for the tips on how to write better Python code.

        For anyone in the future, this is how my REST_FRAMEWORK config looks like:

        REST_FRAMEWORK = {
            'DEFAULT_PERMISSION_CLASSES': (
                'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
            ),
            'DEFAULT_AUTHENTICATION_CLASSES': (
                'rest_framework.authentication.BasicAuthentication',
            )
        }
        
        • Michael Washburn Jr.
          Reply

          You’re very welcome! If you did find this (or other articles useful) please share them with others!

  • Dixon Chaudhary
    Reply

    Hello Michael, I have implemented everything as you have mentioned above but I’m getting this error- ImportError: cannot import name ‘LoginView’. I’m using Django version 1.11.7 What can be the solution for this?

    • Michael Washburn Jr.
      Reply

      Hey there Dixon. I believe the login view is used when using SessionAuthentication. My best guess is that you are trying to import the login view in your URLs file, but don’t have the proper settings in your settings.py.

  • Sam
    Reply

    Hey, thanks for the tutorial! 😀 I particularly appreciate your explanation in regard to the choice of jwt and why to remove rest_framework.authtoken from the django-rest-auth tutorial.

    Question:

    I am trying to test with a curl call:
    curl –request POST \
    –url http://127.0.0.1:8000/rest-auth/login/ \
    –header ‘content-type: application/json’ \
    –data ‘{“username”: “USERNAME”, “password”: “PASSWORD”}’

    And then get this error message:
    {“non_field_errors”:[“Unable to log in with provided credentials.”]}%

    I was a little unclear what exactly to use for the curl call/postman parts…Does this look feasible or am I missing something?

    PS Merry Christmas!

    • Sam
      Reply

      Nevermind — there error’s source was completely unrelated to the content here.

      Again, thanks for the wonderful tutorial!!

  • Huy
    Reply

    Hi,

    Thanks for the tutorial. Not sure if it’s because I’m using Django 2.0, but I got this error while trying to send a GET request:

    ‘JSONWebTokenAuthentication’ object has no attribute ‘has_permission’

    Do you know if it’s because some new changes in ‘rest_framework_jwt’ itself?

    Thank you very much.

    • Michael Washburn Jr.
      Reply

      Hey there, thanks for reading! The django-rest-framework-jwt docs say that they only support up to Django 1.10. It’s very possible that an incompatibility is causing this. However, without seeing more of your implementation it’s hard to say! Let me know what you find out. Try the same code in a Django 1.10 app and compare your results! If there is an incompatibility, maybe we can submit a patch.

  • Klaus
    Reply

    Hi bro¡ I have many questions. I followed the tutorial but i did not find how to implement that in my view’s permission_classes. I apreciate if someone can help me. (sorry for my english I’m not native)

    • Michael Washburn Jr.
      Reply

      Hi Klaus, are you trying to implement custom permission classes? The predefined classes can be set on your view by setting the class’s `permission_classes` variable. See here.

  • Joe Estrella
    Reply

    Really great tutorial.

    Just a question. It seems the line url(r’^’, include(‘rest_auth.urls’)) is overriding the original url(r’^’, include(router.urls)). This disables all routes other than auth’s disbaled, thus, 404.

  • Joe Estrella
    Reply

    Also, would like to know why the homepage suddenly displays an error:

    ‘JSONWebTokenAuthentication’ object has no attribute ‘has_permission’

    I believe it is due to me overriding the default api_root:
    “`

    #urls.py
    ########
    url(r’^$’, api_root),

    ##views.py
    ########
    @api_view([‘ANY’])
    def api_root(request, format=None):
    return Response({
    ‘users’: reverse(‘user-list’, request=request, format=format),
    ‘groups’: reverse(‘group-list’, request=request, format=format),

    “`

  • Divyanshu
    Reply

    For people creating custom API on the basis of this post and wondering how to pass token for API calls –
    please put token in header just like how Author described it for Logout API –

    Authorization: JWT your_token_here

    I would like to request the Author to mention this fact in the post because users would be interested in using the Token for APIs other than authentication too 🙂

    • Michael Washburn Jr.
      Reply

      Hey Divyanshu, thanks for the reply. My full intention here is for the JWT token to be passed as the Authorization header for all API requests. I should have been more clear on that. Thanks for pointing out the fact that you can (and should) be doing this!

      • Fabio
        Reply

        Very nice post! There’s something I can’t find anywhere, how do you manage when a user changes his password or user gets deleted/deactivated? Is there a way to force a token regeneration in that case, or maybe passing through the header everytime emai/psw to check credentials are still valid?

        Thank you!

        • Michael Washburn Jr.
          Reply

          Hey Fabio. I rely on the django-rest-auth endpoints to reset passwords. I’m not positive if they have functionality to delete/deactivate users though. Regarding tokens, the django-rest-framework-jwt package has means of regenerating tokens.

  • Matt
    Reply

    Thanks, Michael, this is awesome tutorial, it would be nice if you would have some tutorial about how to use custom fields…for example I am a newbie to Django, and i even don’t know where to start …

    • Michael Washburn Jr.
      Reply

      Hey Matt, thanks for reading. Are you talking about custom fields for users? For example, recording a second email for a user. If so, I typically just make a new model with a one-to-one relationship with the default django user model. I’ll consider doing a quick tutorial on this fully explaining the process. Thanks for the suggestion!

      • Matt
        Reply

        Thanks for the reply Michael!
        I was talking about additional fields for a user, let’s say for example I want to force a user to add additional required fields during registration like first name, last name, and age. And make an email and last name as required fields for logging in.
        I think a lot of people would like to make some custom modifications, and there is literally no tutorial for that on the internet, except just pieces of info here and there on StackOverflow.

  • VictorLv4
    Reply

    hi nice tutorial, simple and easy to follow, but i get this error ConnectionRefusedError: [Errno 111] Connection refused with Register i tink is someting to do with sending an email how i can fix this ??? sorry about my english not my first language

  • Mike Mittelman
    Reply

    Awesome tutorial Michael!

    This setup is requiring a token on every call. Is there a way to exclude some calls? Reason I need this is that the CORS preflight OPTIONS call sent automatically by the browser is getting a 401 because it doesn’t have the token header and there’s no way to add it.

    HELP!!! 🙂

    Thank you!

    ~Mike

    • Michael Washburn Jr.
      Reply

      That is odd. I haven’t had that problem with the preflight OPTIONS call requiring authentication. Are you sure something hasn’t been mis-configured along the way?

  • Lee
    Reply

    I was able to get this working for registration and login and I’m get my JWT tokens everything works great. Is it possible to use the social account registration/login of test/auth to also with the drf JWT

    • Michael Washburn Jr.
      Reply

      Yes, the two authentication methods should work nicely along side eachother. Simple configure them both according to the docs here and then the social authentication endpoints should also return JWTs. Let me know if that helps

  • Arash
    Reply

    Hi Michael

    Thanks for your article! I’m wondering if one can do the authentication via SSL certificate.
    Do you know how to do it?

    • Michael Washburn Jr.
      Reply

      Hey Arash, I don’t believe Django-Rest-Auth supports certificate-based authentication out-of-the-box. But you could always inherit from the base authentication class and implement your own method. This is something I’ve never had to do before. Best of luck!

  • Ashfaque
    Reply

    Hi Michael! Thanks for the blog. I am using JWT and I wanna know if and how I can persist session data on the server side using this. Let’s say I login in and the server sends the token after authentication, the client stores it and sends it as part of the header for authorization.From login until logout, one can say that its a single user session and in that case how can I maintain the session data on the server side by retrieving the token.Also, how do I do that if the token is refreshed in between login and logout?

  • Scott
    Reply

    The default logout endpoint seems to do nothing except return that the user has logged out. If you take your token and try another GET request with it after logging out, it works just fine. I’m not sure if I forget a step, or you forget a step, or there’s an issue with the JWT integration. Any ideas?

  • errg
    Reply

    Thanks for this tutorial. I’ve been able to get everything working through Postman, but I can’t get a GET request working through the python requests library. Part of my problem is I don’t really understand how Postman is implementing the authentication.

    In particular, when I try to use the requests module, I have something like:
    import requests
    reqauth = requests.post(urllogin, data={‘username’:’name’, ‘password’:’pword’)

    this works and gives me a token

    But when I try to use the token:
    auth = {‘Authorization’: ‘JWT’ + very_long_token}
    requse = requests.get(urluse, headers=auth)

    I get a 403 status code, with json:
    {‘detail’: ‘Authentication credentials were not provided.’}

    I’m fairly new to this, so I may be doing something stupid, any help is appreciated immensely!

    • errg
      Reply

      In rereading the above I had left off the space after ‘JWT’, but in my actual code I did it properly ‘JWT ‘ + …

      I also tried using ‘Bearer’ and ‘Token’ in place of ‘JWT’, but neither of those worked either…

      Thanks very much!

  • Vikas K
    Reply

    Hi Michael I have sent you a request from your contact form regarding the issue with the same context please review it and help me out…thanks!

pingbacks / trackbacks

Leave a Comment