Loading…

Multiple Authentication Tokens per User with Django REST Framework

In this post I will show you how to set up token authentication with Django REST Framework (or short DRF), with the special twist that each user can have multiple different tokens at the same time. This is not possible with DRF out of the box and needs some custom code.

But why would you even want multiple tokens per user? Because you really want to use a different token for each client. If every client uses the same token, the token is basically a password. If you want to change the token in one client, you have to change it in every client.

Enough reasoning, let’s write some code. If you look at the definition of the Token model that comes with DRF, you might notice that it has a severe limitation: it has a One-to-One relationship to the user model, so each user can only have one token.

Luckily, it is quite easy to use a custom token model. First, you have to remove rest_framework.authtoken from your INSTALLED_APPS. Then create your own token model as a subclass of DRF’s Token:

from django.conf import settings
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _

import rest_framework.authtoken.models


@python_2_unicode_compatible
class Token(rest_framework.authtoken.models.Token):
    # key is no longer primary key, but still indexed and unique
    key = models.CharField(_("Key"), max_length=40, db_index=True, unique=True)
    # relation to user is a ForeignKey, so each user can have more than one token
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='auth_tokens',
        on_delete=models.CASCADE, verbose_name=_("User")
    )
    name = models.CharField(_("Name"), max_length=64)

    class Meta:
        unique_together = (('user', 'name'),)

Our custom model is hiding some fields inherited from the superclass. Field hiding works only for abstract models, but the Token class is abstract
as long as the rest_framework.authtoken app is not installed. You did remove it from your INSTALLED_APPS, right?

Note that hiding of fields from abstract models is a new feature in Django 1.10. In older versions of Django, field hiding is not permitted. If you are not using Django 1.10 yet, the easiest workaround is probably to just copy the required code from DRF.

You can put our Token model in an existing app or create a new one for it. If you decide to do the latter, don’t forget to add it to INSTALLED_APPS!

How is the new Token model different?

  • The OneToOne relationship to user model has been replaced with a ForeignKey, allowing multiple tokens per user
  • The key is indexed and unique, but no longer a primary key. That means Django will create a numeric primary key by default. Why does that make sense? If you have multiple tokens, you might want to have a way to manage them. That usually means referring to tokens by a URL. As the key is supposed to be secret, it should not appear in URLs, because URLs usually end up in a kind of places, e.g. server logs.
  • Tokens now have a name to be able to differentiate them. The name should be unique per user

Now you have to tell DRF to use our custom token class. For that, simply create a subclass of TokenAuthentication and override the model attribute. You can put that class in any module you want, I prefer creating a file called authentication.py in the same app as the token model.

import rest_framework.authentication

from .models import Token


class TokenAuthentication(rest_framework.authentication.TokenAuthentication):
    model = Token

Use this class in authentication_classes and you are all set! You can now create as many tokens as you want.

>>> user = User.objects.get(username='daniel')
>>> Token.objects.create(user=user, name='Foo')
>>> Token.objects.create(user=user, name='Bar')

Summary

So what did we do today?

  1. We created a custom Token model with a ForeignKey to User instead of a OneToOneField
  2. We created a custom authentication class that uses our Token

What’s next? If your site is already online and has tokens that are in use, you might want to create a data migration to keep your users happy. You also need some kind of management interface to manage the tokens. This could be handled in the admin or as part of some kind of account settings page. For now, both is left as an exercise to the reader.

If you have questions or anything to add, leave a comment below!

16 thoughts on “Multiple Authentication Tokens per User with Django REST Framework

  1. Thank. This is very useful for me. However I need to stick with Django 1.9. What code do I need to copy from DRF to make it work?
    Thanks

  2. Thanks I tried copying and wasnt successful. I also tried to upgrade to 1.11 with more success. However it seems I still have to rewerite the TokenAuthentication class to be able to support multiple tokens per user and take into account the name field. I use djoser project to expose REST Apis for registration/login etc. Thanks

    1. Tokens now have a name to be able to differentiate them. The name should be unique per user

      The primary purpose is to keep track where a token is used. You could store the name of a system or client application that uses this token.

      That enables you to revoke a token without guessing which token is the right one.

  3. hi.that is good solution .i can create multi token for one user but my other api’s show this
    Error : type object ‘Token’ has no attribute ‘DoesNotExist’

    can you help me for solve this problem ?

      1. My ‘Token’ model extended from : ‘rest_framework.authtoken.models.Token’ (According to the article)
        ‘Django’ version is 2.1.1
        ‘restframework’ version is 3.8.2

        1. If you haven’t done so yet, I suggest you post your question with the relevant code and complete stack trace to Stack Overflow. Feel free to post the link to the question here or to send it directly to me. You can find my email on the About page.

  4. Hi,
    I have implemented your code (and mix with this https://matthiasomisore.com/web-programming/django-drf-create-multiple-token-per-user/ because its exactly what i need.) and it works perfectly.

    But now I have the question of whether it is possible to integrate the class of this token with other libraries like (django-rest-auth or Djoser) that provide mechanisms for registering and authenticating users using the original TokenAuthentication class of rest_framework. My idea is avoid re implementing authentication and registration, just use those librarys with the new token class which support multiple authentication tokens per user
    but I do not know how to face it.

    Django-rest-auth: https://github.com/Tivix/django-rest-auth / https://django-rest-auth.readthedocs.io/en/latest/introduction.html
    Djoser: https://github.com/sunscrapers/djoser / https://djoser.readthedocs.io/en/latest/

    Thank you very much and i’am apologize about my english 🙂

    1. Hi, other libraries that use DRF Token will assume that there is only one token per user. If you have multiple tokens, they will probably fail because they don’t have any logic to choose one the available tokens.

      You can try to use both DRF’s default TokenAuthentication class and the custom TokenAuthentication class. So each user will have one DRF token that the libraries use, and can have multiple additional tokens that you have to manage yourself.

  5. My scenario is, when user send login request, I will send him a unique auth token created by your way which will have multiple auth tokens. But when I want to access Get apis from this token while from POSTMAN with Header, it’ll return {“detail”: “Authentication credentials were not provided.”}. At that time, my settings is
    REST_FRAMEWORK = {
    ‘DEFAULT_AUTHENTICATION_CLASSES’:[
    ‘rest_framework.authentication.BasicAuthentication’,
    ‘rest_framework.authentication.SessionAuthentication’,
    ],
    ‘DEFAULT_PERMISSION_CLASSES’: [
    ‘rest_framework.permissions.IsAuthenticated’,
    ]
    }

    But I change my settings to this:
    REST_FRAMEWORK = {
    ‘DEFAULT_AUTHENTICATION_CLASSES’:[
    ‘rest_framework.authentication.BasicAuthentication’,
    ‘rest_framework.authentication.SessionAuthentication’,
    ‘rest_framework.permissions.IsAuthenticated’,
    ],
    ‘DEFAULT_PERMISSION_CLASSES’: [
    ]
    }

    it’ll throw this error : rest_framework.request.WrappedAttributeError: ‘IsAuthenticated’ object has no attribute ‘authenticate’.

    Stuck between this. Please help me.

    What i want to do is, to make use of IsAuthenticated decorator to access only logged in user to make access this url with dedicated token.
    @api_view([“GET”])
    @permission_classes([IsAuthenticated])
    def dashboard(request):

    Please do needful.

    1. After adding, ‘rest_framework.authentication.TokenAuthentication’, class to seetings.DEFAULT_AUTHENTICATION_CLASSES, I’m facing this error.

      WrappedAttributeError at /user/dashboard
      type object ‘Token’ has no attribute ‘DoesNotExist’

      Please solve this.

Leave a Reply