Search code examples
pythondjangoauthenticationdjango-rest-frameworkapi-key

Api key and Django Rest Framework Auth Token


I'm already using build-in Django rest auth token and I plan to release an other api that will be called by an external integrations to call some action in my Django application. The issue is that I want to generate an other token for this external api call that must be separate from auth system (f.i. like Mandrill API Keys or Github Personal Access Token). Is it a good solution to generate api keys from Django rest framework authtoken Model ?

External api token:

  • must never expire (it could expire in a session auth system)
  • could be linked to user but not required (if linked to account)
  • could be revoked and reactivated

Do you have any experience with releasing api keys ?

Is it any best practice recommended by Django Rest Framework ?

Thank you ;)


Solution

  • I have created a new authentication backend and a new token model to avoid side effect on build-in token behaviour.

    models.py

    class ApiKeyToken(models.Model):
        key = models.CharField(max_length=40, primary_key=True)
        company = models.ForeignKey(Company)
        is_active = models.BooleanField(default=True)
    
        def save(self, *args, **kwargs):
            if not self.key:
                self.key = self.generate_key()
            return super(ApiKeyToken, self).save(*args, **kwargs)
    
        def generate_key(self):
            return binascii.hexlify(os.urandom(20)).decode()
    
        def __unicode__(self):
            return self.key
    

    authentication.py

    class ApiKeyAuthentication(TokenAuthentication):
    
        def get_token_from_auth_header(self, auth):
            auth = auth.split()
            if not auth or auth[0].lower() != b'api-key':
                return None
    
            if len(auth) == 1:
                raise AuthenticationFailed('Invalid token header. No credentials provided.')
            elif len(auth) > 2:
                raise AuthenticationFailed('Invalid token header. Token string should not contain spaces.')
    
            try:
                return auth[1].decode()
            except UnicodeError:
                raise AuthenticationFailed('Invalid token header. Token string should not contain invalid characters.')
    
        def authenticate(self, request):
            auth = get_authorization_header(request)
            token = self.get_token_from_auth_header(auth)
    
            if not token:
                token = request.GET.get('api-key', request.POST.get('api-key', None))
    
            if token:
                return self.authenticate_credentials(token)
    
        def authenticate_credentials(self, key):
            try:
                token = ApiKeyToken.objects.get(key=key)
            except ApiKeyToken.DoesNotExist:
                raise AuthenticationFailed('Invalid Api key.')
    
            if not token.is_active:
                raise AuthenticationFailed('Api key inactive or deleted.')
    
            user = token.company.users.first()  # what ever you want here
            return (user, token)
    

    Then you can request secured api with:

    curl http://example.com/api/your-awesome-api.json -H "Authorization: Api-Key {token}"