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 17 comments
  • Quentin
    Reply

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

  • 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.

  • 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!

Leave a Comment