Django Rest Framework Authentication Tutorial
In this tutorial we’ll create a Django To Do app, add an API with Django Rest Framework, and then add user authentication to our API with django-rest-auth. Although you should use a custom user model for all Django projects, we will not here in the interests of simplicity. I’ll cover how to use a custom user model with Django Rest Framework auth in a future post.
You should have a basic understanding of how Django works and a local development configuration with Python 3 and pipenv. I’ve written a free book called Django for Beginners that introduces Django and has a dedicated chapter on local dev setup.
Complete source code can be found on Github.
Initial Setup
Let’s start by creating a directory for our code, installing Django, and creating a new project. Execute the following on the command line.
$ cd Desktop
$ mkdir drf-rest-auth && cd drf-rest-auth
$ pipenv install django
$ pipenv shell
(drf-rest-auth) $ django-admin startproject demo_project .
(drf-rest-auth) $ python manage.py startapp todos
We’re placing the code on the Desktop
but it can live anywhere you choose on your computer. We’ve used pipenv to install Django and then enter a dedicated virtual environment. And finally created a new Django project called demo_project
and a todos
app.
Since we have a new app we need to update our INSTALLED_APPS
setting. Open demo_project/settings.py
with your text editor and add todos
at the bottom of the list.
# demo_project/settings.py
INSTALLED_APPS = [
...
'todos',
]
Let’s do our first migration to set up the initial database at this point.
(drf-rest-auth) $ python manage.py migrate
Next we can create a basic model for our Todo
app which will just have a title
, description
, and add a __str__
method.
# todos/models.py
from django.db import models
class Todo(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
def __str__(self):
"""A string representation of the model."""
return self.title
Then create a dedicated migration file and migrate our change to the project database.
(drf-rest-auth) $ python manage.py makemigrations todos
(drf-rest-auth) $ python manage.py migrate todos
The final step is update the admin.py
file so the todos
app is displayed in the Django admin.
# todos/admin.py
from django.contrib import admin
from .models import Todo
admin.site.register(Todo)
And…we’re done. We can now create a superuser account and log into the admin for the first time.
(drf-rest-auth) $ python manage.py createsuperuser
(drf-rest-auth) $ python manage.py runserver
Navigate to http://127.0.0.1:8000/admin and login with your new superuser account.
On the Admin homepage you should see the todos
app. Click on “+ Add” button for Todos and add two new entries we can use.
In a normal Django app at this point we would add urls, views, and templates to display our content. But since our end goal is an API that will transmit the data over the internet, we don’t need to! Instead we can jump right into using Django Rest Framework at this point.
Django Rest Framework
To start we need to install Django Rest Framework and then create a new api
app.
(drf-rest-auth) $ pipenv install djangorestframework
(drf-rest-auth) $ python manage.py startapp api
Next add both rest_framework
and the api
app to our INSTALLED_APPS
setting. We also add default permissions so that anyone can access our API. In the real world you would never do this: setting permissions is quite granular, but for demoing purposes we’ll just leave everything open. This is just running locally after all so there’s no real security risk.
# demo_project/settings.py
INSTALLED_APPS = [
...
'rest_framework',
'api',
'todos',
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]
}
Every API endpoint we create needs a serializer, url, and view. The serializer transforms our Django model data into a format that can be transmitted over the web, usually as JSON. The view applies logic to our data, and the url controls the routing.
So while a traditional Django app uses urls, views, and templates, a DRF app uses urls, views, and serializers. This will make more sense in a minute.
Within our api
app create a serializers.py
file and also a dedicated urls.py
file.
(drf-rest-auth) $ touch api/serializers.py api/urls.py
Update our project-level urls.py
file by adding include
on the second line and adding an API route. Here we’re putting everything at api/v1/
. It’s a best practice to version your API since it will likely change in the future. This way a future version can be at api/v2/
without breaking anything.
# demo_project/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('api.urls')),
]
In my experience the concept of serializers is the hardest to internalize for beginners. I like to think of a normal Django project as one contained thing that outputs webpages. But an API sends out just data via API endpoints, so there’s no HTML, CSS, JavaScript or anything else transmitted. It’s just pure data in JSON format. Under the hood DRF handles most of this for us.
# api/serializers.py
from rest_framework import serializers
from todos import models
class TodoSerializer(serializers.ModelSerializer):
class Meta:
fields = (
'id',
'title',
'description',
)
model = models.Todo
Here we’ve created a dedicated serializer for our ToDo
model and specified the fields we want exposed. This includes the id
field Django automatically adds to every database model, title, and description. We could limit the fields here–you often don’t want to expose everything–but for now we won’t.
Next up is our view. DRF views are very similar to traditional Django views and even come with several generic views that provide lots of functionality with a minimal amount of code on our part. We want to create just a ListView and DetailView for our API.
# api/views.py
from rest_framework import generics
from todos import models
from . import serializers
class ListTodo(generics.ListCreateAPIView):
queryset = models.Todo.objects.all()
serializer_class = serializers.TodoSerializer
class DetailTodo(generics.RetrieveUpdateDestroyAPIView):
queryset = models.Todo.objects.all()
serializer_class = serializers.TodoSerializer
Finally we need to update the urls.py
file in our our api
app to display our two views. The ListView will be available at api/v1
and the DetailView will be at api/v1/1
for the first post with an id
of 1, at api/v1/2
for the second post with an id
of 2, and so on.
# api/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.ListTodo.as_view()),
path('<int:pk>/', views.DetailTodo.as_view()),
]
Ok, we’re done! Now start up the local server and we can see another cool part of Django Rest Framework: its browsable API.
(drf-rest-auth) $ python manage.py runserver
The ListView is at http://127.0.0.1:8000/api/v1/.
And the DetailView is at http://127.0.0.1:8000/api/v1/1/.
To reinforce the idea that data is being sent by our API via JSON we can also use curl
from the command line. In a new command line console–we want to keep our server running so it can process requests–run curl http://localhost:8000/api/v1/
to see a raw JSON view of the ListView endpoint.
$ curl http://localhost:8000/api/v1/
[{"id":1,"title":"1st todo","description":"Learn DRF."},{"id":2,"title":"2nd item","description":"Learn Python too."}]
We can also view the endpoint for a DetailView with curl http://localhost:8000/api/v1/1/
. Make sure to include a closing /
here or the request won’t work.
curl http://localhost:8000/api/v1/1/
{"id":1,"title":"1st todo","description":"Learn DRF."}
And we can POST a new item too with curl -X POST http://localhost:8000/api/v1/ -d "title=hello world&description=hi there"
.
$ curl -X POST http://localhost:8000/api/v1/ -d "title=hello world&description=hi there"
{"id":3,"title":"hello world","description":"hi there"}
If you look over at the command line tab running the server you’ll see a 201
response which tells us the POST request was successful.
[14/Mar/2018 10:44:43] "POST /api/v1/ HTTP/1.1" 201 55
Using the built-in DRF web browser is a much better experience than the command line for testing but it’s useful to know that you can use many different tools–curl, Postman–to explore your API.
User Authentication
On the authentication section of the Django Rest Framework website there are many different approaches mentioned. Without getting into a detailed discussion of their respective pros/cons, you’re best bet is probably to use TokenAuthentication if there’s any chance your API will connect with non-web clients like a native desktop or iOS/Android app.
The third-party django-rest-auth library provides a set of pre-configured API endpoints for login, logout, registration, and can support social authentication too. We’ll use it here.
First step is to install django-rest-auth
and add it to our INSTALLED_APPS
.
(drf-rest-auth) $ pipenv install django-rest-auth
# demo_project/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken', # new!
'rest_auth', # new!
'api',
'todos',
]
Note that we’ve added two lines here: both rest_framework.authtoken
which is Django Rest Framework’s token auth app and also rest_auth
which uses it.
Then update our api/urls.py
file. Make sure to add include
as a top-line import and then the rest_auth
package at rest-auth
.
# api/urls.py
from django.urls import include, path
from . import views
urlpatterns = [
path('', views.ListTodo.as_view()),
path('<int:pk>/', views.DetailTodo.as_view()),
path('rest-auth/', include('rest_auth.urls')),
]
And that’s literally it. Now we can migrate our changes and spin up the server to see what django-rest-auth
has provided.
(drf-rest-auth) $ python manage.py migrate
(drf-rest-auth) $ python manage.py runserver
We have a working login endpoint at http://127.0.0.1:8000/api/v1/rest-auth/login/.
And a logout endpoint at http://127.0.0.1:8000/api/v1/rest-auth/logout/.
On the login endpoint page, at the bottom, use your superuser credentials and click on the “POST” button.
The result is a token for the user which can be used to authenticate future HTTP requests!
If you navigate over to the admin section again at http://127.0.0.1:8000/admin/ you can see there’s now an additional AUTH TOKEN
section.
Click on it and you’ll see the token–named a KEY
–for the only user. You can create new users within the Admin section, access the Login API endpoint, and it will generate a unique token for each new user.
Next Steps
django-rest-auth
also provides support for user registration, password reset/confirm/change, and even social media authentication. I’ll try to cover these in a future post too.
However for now with a small amount of code we’ve built a Django app, added the Django Rest Framework for API endpoints, and then used django-rest-auth
to include authentication. Not bad!