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
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
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
How is the new Token model different?
OneToOnerelationship 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')
So what did we do today?
- We created a custom Token model with a ForeignKey to User instead of a OneToOneField
- 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!