I have enabled user authentication with DRF using TokenAuthentication
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication'
),
'DEFAULT_MODEL_SERIALIZER_CLASS':
'rest_framework.serializers.ModelSerializer',
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
#'EXCEPTION_HANDLER': 'apps.core.exceptions.custom_exception_handler'
}
I have the following model:
class Device(CreationModificationMixin):
"""
Contains devices (WW controllers). A device may be associated with the Owner
"""
_STATUSES = (
('A', 'Active'), # when everything is okay
('I', 'Inactive'), # when we got nothing from SPA controllers for X minutes
('F', 'Failure'), # when controller says it has issues
)
_TYPES = (
('S', 'Spa'),
('P', 'Pool'),
)
udid = models.CharField(max_length=255, verbose_name="Unique ID / MAC Address", help_text="MAC Address of WiFi controller", unique=True, null=False, blank=False, db_index=True)
type = models.CharField(max_length=1, choices=_TYPES, null=False, blank=False)
title = models.CharField(max_length=255, null=False, blank=False, db_index=True)
status = models.CharField(max_length=1, default='A', choices=_STATUSES)
pinged = models.DateTimeField(null=True)
owner = models.ForeignKey(Owner, verbose_name="Owner", null=True, blank=True, db_index=True)
def __str__(self):
return self.udid
This represents hardware device that will be sending discrete requests to API endpoints, therefore I need to authenticate each request and ideally with token based identification, like
POST /api/devices/login
{
udid: '...mac address...',
hash: '...sha256...hash string',
time: '2015-01-01 12:24:30'
}
hash will be calculated on device side as sha256(salt + udid + current_time) the same hash will be calculated on DRF side inside /login to compare and generate token that will be saved in REDIS and returned back with response.
All future requests will be passing this token as a header, which will be checked in custom Permission class.
my questions:
Where should I put this functionality?
As @daniel-van-flymen pointed out, it's probably not a good idea to return a device instead of a user. So what I did was create a DeviceUser
class that extends django.contrib.auth.models.AnonymousUser
and return that in my custom authentication (devices are essentially anonymous users, after all).
from myapp.models import Device
from rest_framework import authentication
from django.contrib.auth.models import AnonymousUser
from rest_framework.exceptions import AuthenticationFailed
class DeviceUser(AnonymousUser):
def __init__(self, device):
self.device = device
@property
def is_authenticated(self):
return True
class DeviceAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
udid = request.META.get("HTTP_X_UDID", None)
if not udid:
return None
try:
device = Device.objects.get(udid=udid)
except Device.DoesNotExist:
raise AuthenticationFailed("Invalid UDID")
if not device.active:
raise AuthenticationFailed("Device is inactive or deleted")
request.device = device
return (DeviceUser(device), None)
This code lives in myapp.authentication
, you can then add the following to your settings:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"myapp.authentication.DeviceAuthentication",
)
}
A couple of notes from your original spec: I've modified the request in the authenticator to include the device, so you can do request.device.is_authenticated
; however, the user will be a DeviceUser
so you could also do request.user.device.is_authenticated
(so long as you do the appropriate checks for the device
attribute).
Your original spec also asked to implement TokenAuthentication
, and it is possible to subclass this authentication class to use it more directly; for simplicity, I'm just having the device include the X-UDID header in their request.
Also note that as with the token authentication mechanism, you must use this method with HTTPS, otherwise the UDID
will be sent in plain text, allowing someone to impersonate a device.
You can subclass DRF's BaseAuthentication class and override the .authenticate(self, request) method. On successful auth this function should return (device, None). This will set device object in request.user property. You can implement is_authenticated() in your Device model class.
class APICustomAuthentication(BaseAuthentication):
---
def authenticate(self, request):
----
return (device, None) # on successful authentication
Add APICustomAuthentication to 'DEFAULT_AUTHENTICATION_CLASSES' in settings.
More details are available here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With