Rest_Framework APIClient tests return 401 Unauthorized with Token Authentication

IAmKale

I'm writing some tests for my rest_framework API, and I'm using token authentication to secure it. I've decided to use DRF's APIClient class to simulate calls from a user's browser.

I can grab tokens just fine from the API by hitting the authentication endpoint, but when I try to use those tokens to authenticate any further requests to other endpoints, I get back a 401 Unauthorized error with the message, "Invalid token".

Curiously, I can copy-paste the exact same token and make a successful, manual GET request to that exact same endpoint via something like HTTPIE...

Here's my tests.py:

import json

from rest_framework import status
from rest_framework.test import APIClient
from rest_framework.test import APITestCase


class TestUser(object):
    """
    A basic user class to simplify requests to the API
    Tokens can be generated by authing as a user to /v1/auth/
    """
    def __init__(self, token):
        self.client = APIClient()
        self.token = token
        self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)

    def get(self, url):
        print("Token: {0}".format(self.token))
        res = self.client.get(url)
        print('GET {0}: {1}'.format(url, res.data))
        return res

    def post(self, url, data):
        res = self.client.post(url, data, format='json')
        print('POST {0}: {1}'.format(url, res.data))
        return res

    def patch(self, url, data):
        res = self.client.patch(url, data, json=data)
        print('PATCH {0}: {1}'.format(url, res.data))
        return res

    def delete(self, url):
        res = self.client.delete(url)
        print('DELETE {0}: {1}'.format(url, res.data))
        return res


# Grab new tokens every time we run our tests
auth_client = APIClient()

SUPERUSER = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser',
                                      'password': 'password'}).data['token'])
ADMIN = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser4',
                                  'password': 'password'}).data['token'])
MANAGER = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser2',
                                    'password': 'password'}).data['token'])
EMPLOYEE = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser3',
                                     'password': 'password'}).data['token'])


class AdminSiteCompanies(APITestCase):
    def test_list_crud_permissions(self):
        # GET
        url = "/v1/admin_site/companies/"
        self.assertEqual(SUPERUSER.get(url).status_code, status.HTTP_200_OK)
        self.assertEqual(ADMIN.get(url).status_code, status.HTTP_200_OK)
        self.assertEqual(MANAGER.get(url).status_code, status.HTTP_403_FORBIDDEN)
        self.assertEqual(EMPLOYEE.get(url).status_code, status.HTTP_403_FORBIDDEN)

This is console output from the above test showing that a valid token is received from the API, just before it spits back a 401 when I try to use it in a test:

Creating test database for alias 'default'...
Token: d579dbe4980d8ac451a462fc78cf38f789decddf
GET /v1/admin_site/companies/: {'detail': 'Invalid token.'}
Destroying test database for alias 'default'...

Here's console output from me making a successful manual GET request using HTTPIE and the above token:

D:\Projects\API-Server>http http://127.0.0.1:8000/v1/admin_site/companies/ "Authorization: Token d579dbe4980d8ac451a462fc78cf38f789decddf"
HTTP/1.0 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Date: Fri, 01 May 2015 05:43:59 GMT
Server: WSGIServer/0.2 CPython/3.4.3
Vary: Accept
X-Frame-Options: SAMEORIGIN

[
    {
        "address": "1234 Fake Street",
        "id": 1,
        "name": "FedEx",
        "shift_type": "OE"
    },
    {
        "address": "Bolivia",
        "id": 2,
        "name": "UPS",
        "shift_type": "PS"
    }
]

Here's the relevant bits from my settings.py:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',
    'serverapp',
    'rest_framework_swagger',
)

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'serverapp.middlewares.EmployeeMiddleware',
)

ROOT_URLCONF = 'shiftserver.urls'

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_FILTER_BACKENDS': (
        'rest_framework.filters.DjangoFilterBackend',
    )
}

# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

This is the first time I've ever written tests for Django/rest_framework, so I've been diligently following DRF's documentation on testing and authenticating. No matter what I try, though, I still can't get past this "invalid token" issue.

A friend who's way more versed in DRF than me was left stumped when I asked him for help with this, so hopefully you guys can reveal what we're both missing.

IAmKale

I figured it out! POSTing to the API outside of a TestCase class hits the actual API server that I happened to have running while I was running my tests. I refactored AdminSiteCompanies(APITestCase) to set up test data, users, and authenticate those users all within the class's setUp(self):

class AdminSiteCompanies(APITestCase):
    def setUp(self):
        # Create test Objects here
        ...snip...

        # Create test Users here
        # SuperUser
        create_user('TestUser', 'password', '[email protected]', True, False, False, co1lo1.id)
        ...snip...

        # Grab new tokens every time we run our tests
        # APIClient allows us to emulate calls from a browser
        auth_client = APIClient()

        # Authenticate our users
        self.SUPERUSER = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser', 'password': 'password'})
                                  .data['token'])
        ...snip...

    def test_list_crud_permissions(self):
        # GET
        url = "/v1/admin_site/companies/"
        self.assertEqual(self.SUPERUSER.get(url).status_code, status.HTTP_200_OK)
        # ^ Now passes test
        ...snip...

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Django Rest Framework Token Authentication

Spring Boot bearer token authentication giving 401

Authentication is returning "401 (Unauthorized)" when it shouldn't

How to return 401 when Auth Token is missing in Django Rest Framework

Aurelia Windows Authentication - Post 401 Unauthorized

How to add authentication token in header of `APIClient` in `django rest_framework test`

Django Rest Framework: HTTP 401 Unauthorized error

How to return unauthorized using Laravel API Authentication

Spring Security REST - Unit Tests fail with HttpStatusCode 401 Unauthorized

Handling 401 (Unauthorized) angularjs Authentication using JWT

Consuming a WCF REST service with Basic authentication using HttpClient.GetAsync() results in (401) Unauthorized

Token authentication in django (rest_framework) not working

JHipster: Rest service calls return "401 Unauthorized" when routing through the gateway

cloud endpoints return error 401 unauthorized request

401 - Unauthorized: Access is denied due to invalid credentials. For windows authentication

Token Based Authentication using ASP.NET Web API 2 and Owin throws 401 unauthorized

Asana API Personal Access Token return 401 (Unauthorized)

Mix of Forms Authentication and OAuth for Web API project doesn't return 401 in case of expired token

Laravel API Authentication using Passport results in 401 (Unauthorized)

401 UnAuthorized - This request requires HTTP authentication - Payara/Glassfish

Can't use Dynamics CRM token (401 Unauthorized)

Return a authentication token

Token Authentication with django rest framework

Web API (.NET Framework) Azure AD Authentication always returns 401 Unauthorized

Custom rest_framework authentication not getting called

Nestjs Authentication Project: "statusCode": 401, "message": "Unauthorized"

spring boot rest app secured by keycloak 18 return always error 401 Unauthorized

rest_framework_swagger installation - HTTP 401 Unauthorized

Laravel 10 Token authentication with Sanctum returns "401 Unauthorized"