Krzysztof Żuraw Blog

JSON Web Tokens in django application- part three

October 30, 2016

As we have working application now it’s high time to make it more secure by authenticating users. To do this I will use JSON Web Tokens.

JWT in Django Rest Framework

There are few packages on pypi that provide JWT support but as I am already using DRF I choose package called REST framework JWT Auth. It’s simple package and does it’s job well so I can recommend it to everyone. But you have to make sure that your application is behind SSL/TLS as JWT tokens generated are not signed. But enough writing- let’s jump into the code.

Implementing JWT in DRF application

First I added small change to my Task model definition in models.py:

class Task(models.Model):
    # rest of model
    person = models.ForeignKey('auth.User', related_name='tasks')
    # rest of model

It is the same model definition but written using string. The code in Django responsible for model lookup based on the string can be seen here.

Then I added an additional field to UserSerializer- thanks to that when getting info about the user I also get info about which tasks this user has. It can be accomplished by:

class UserSerializer(serializers.ModelSerializer):
    tasks = serializers.PrimaryKeyRelatedField(
        many=True, queryset=Task.objects.all()
    )

    # rest of the code

As I got my models and serializers ready I need views:

from rest_framework import permissions


class TaskViewSet(viewsets.ModelViewSet):
    # rest of the code

    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)


class UserViewSet(viewsets.ModelViewSet):
    # rest of the code

    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

I added permission_classes to tell DRF that these views are read only when the user is not authenticated. If I send a token ( or authenticate in another way) I am able to modify data kept under this view. To authenticate I needed a new endpoint so there’s a small change to urls.py:

from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    # rest of the code
    url(r'^api-auth/', obtain_jwt_token),
]

Right now the user firsts need to authenticate using this endpoint. In return, endpoint gives back a token. Last thing to let this work is to tell Django Rest Framework that I want to use JWT as a basic type of authentication in settings.py:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    )
}

And that’s it! JWT should be working:

$ http GET 127.0.0.1:9000/tasks/
HTTP/1.0 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Date: Sat, 29 Oct 2016 13:10:52 GMT
Server: WSGIServer/0.2 CPython/3.5.1
Vary: Accept
X-Frame-Options: SAMEORIGIN

[
  {
      "due_to": "2016-10-18T19:12:01Z",
      "person": "admin",
      "title": "First one",
  },
  {
      "due_to": "2016-10-18T19:12:10Z",
      "person": "admin",
      "title": "Second one",
  }
]

$ cat create_task.json
{
  "due_to": "2016-10-18T19:12:01Z",
  "person": 1,
  "title": "Next one",
}

$ http POST 127.0.0.1:9000/tasks/ < create_task.json
HTTP/1.0 401 Unauthorized
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Date: Sun, 30 Oct 2016 08:38:41 GMT
Server: WSGIServer/0.2 CPython/3.5.1
Vary: Accept
WWW-Authenticate: JWT realm="api"
X-Frame-Options: SAMEORIGIN

{
    "detail": "Authentication credentials were not provided."
}

To send POST you need:

$ http POST 127.0.0.1:9000/api-auth/ username=admin password=admin
HTTP/1.0 200 OK
Allow: POST, OPTIONS
Content-Type: application/json
Date: Sun, 30 Oct 2016 08:41:26 GMT
Server: WSGIServer/0.2 CPython/3.5.1
Vary: Accept
X-Frame-Options: SAMEORIGIN

{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0Nzc4MTc4NTMsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6ImFkbWluIiwidXNlcl9pZCI6MX0.xWlhwgzzVjDwgTPp48AgAYDJnraGThlkGmBnJbKnA74"
}


$ http POST 127.0.0.1:9000/tasks/ < create_task.json 'Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0Nzc4MTc4NTMsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6ImFkbWluIiwidXNlcl9pZCI6MX0.xWlhwgzzVjDwgTPp48AgAYDJnraGThlkGmBnJbKnA74'
HTTP/1.0 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Date: Sun, 30 Oct 2016 08:53:30 GMT
Server: WSGIServer/0.2 CPython/3.5.1
Vary: Accept
X-Frame-Options: SAMEORIGIN

{
    "due_to": "2016-10-18T19:12:01Z",
    "id": 5,
    "person": 1,
    "title": "Next one"
}

That’s all for today! Feel free to comment and check repo for this blog post under this link.

Special thanks to Kasia for being editor for this post. Thank you.

Tagged with python django jwt

I turned off Disqus comments. If you want to give me feedback please write to krzysztof.zuraw(at)fastmail.com or use Keybase.


Krzysztof ŻurawDelivered by Krzysztof Żuraw. Opinions are my own. You can follow updates via RSS feed.