1

I'm just trying to understand djangorestframework-simplejwt expiration and want to know what is returned when the token expires.

To explore this a wrote a simple testcase in my django project, but I can't seem to get the expire to occur.

views.py

import json
from http import HTTPStatus

from django.http import JsonResponse
from rest_framework.decorators import api_view

from .models import Visitor


@api_view(["POST"])
def visitor_post(request):
    body_unicode = request.body.decode("utf-8")
    if not body_unicode.strip():
        return JsonResponse({"status": HTTPStatus.BAD_REQUEST}, status=HTTPStatus.BAD_REQUEST)

    body = json.loads(body_unicode)
    submitted_datetime = body["submitted_datetime"]

    visitor = Visitor(
        representative_name=body["representative_name"],
        visitor_type=body["visitor_type"],
        visitor_count=body["visitor_count"],
        submitted_datetime=submitted_datetime,
    )
    visitor.save()
    return JsonResponse({"status": HTTPStatus.CREATED}, status=HTTPStatus.CREATED)

Currently my testcase is as follows:

tests.py

import datetime
import json
from time import sleep
from http import HTTPStatus
from typing import Optional, Tuple

from accounts.models import CustomUser
from django.urls import reverse
from django.utils import timezone
from django.test import override_settings
from rest_framework.test import APIClient, APITestCase


EXPIRE_WAIT_SECONDS = 5
SIMPLE_JWT_EXPIRE_TEST_SETTINGS = {
    "ACCESS_TOKEN_LIFETIME": datetime.timedelta(seconds=EXPIRE_WAIT_SECONDS),
    "REFRESH_TOKEN_LIFETIME": datetime.timedelta(days=14),
    "ROTATE_REFRESH_TOKENS": True,
    "BLACKLIST_AFTER_ROTATION": False,
    "ALGORITHM": "HS256",
    "SIGNING_KEY": 'kkdkasjf;a',
    "VERIFYING_KEY": None,
    "AUTH_HEADER_TYPES": ("JWT",),
    "USER_ID_FIELD": "id",
    "USER_ID_CLAIM": "user_id",
    "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
    "TOKEN_TYPE_CLAIM": "token_type",
}


class ViewsTestCase(APITestCase):
    def setUp(self):
        self.valid_user = CustomUser(last_name="user", first_name="valid", username="validuser", email="validuser@email.com")
        self.valid_user_password = "mysecretpassword"
        self.valid_user.set_password(self.valid_user_password)
        self.valid_user.save()
        self.apiclient = APIClient()

    def _get_jwt_token(self, username: Optional[str] = None, password: Optional[str] = None) -> Tuple[str, str]:
        if not username:
            username = self.valid_user.username
        if not password:
            password = self.valid_user_password
        body = {"username": username, "password": password}
        url = reverse("token_obtain_pair")
        response = self.apiclient.post(url, data=body, format="json")
        self.assertEqual(response.status_code, 200)
        token_data = json.loads(response.content)
        return token_data["access"], token_data["refresh"]

    @override_settings(SIMPLE_JWT=SIMPLE_JWT_EXPIRE_TEST_SETTINGS)
    def test_visitor_post_token_expire(self):
        access_token, _ = self._get_jwt_token()

        now = timezone.now()
        data = {
            "representative_name": "Somename",
            "visitor_count": 1,
            "submitted_datetime": now.isoformat(),
        }
        sleep(EXPIRE_WAIT_SECONDS + 1)
        self.apiclient.credentials(HTTP_AUTHORIZATION=f"JWT {access_token}")
        url = "/visitors/"
        response = self.apiclient.post(url, data=data, format="json")
        self.assertEqual(response.status_code, HTTPStatus.UNAUTHORIZED)

I expect this to result in some kind of authorization error, like a 401 or something, but it seems to still create the protected view.

self.assertEqual(response.status_code, HTTPStatus.UNAUTHORIZED)AssertionError: 201 != HTTPStatus.UNAUTHORIZED: 401

Why doesn't the token expire? Or, how can I make the token expire?

Maybe I don't understand this correctly.

I my understanding is that when ACCESS_TOKEN_LIFETIME expires you can no longer use the access_token and need to get a new token using the refresh token. Is that correct?

monkut
  • 42,176
  • 24
  • 124
  • 155
  • 1
    The answer yo your last question is yes. Maybe the settings overriding is not working as you expect. Print the generated token and check the expiration date, for example pasting it in https://jwt.io/ – gmc Jun 05 '20 at 16:08
  • Thanks! Yes, the override isn't working as expected, and the expire is keeping it's initial 5 minutes. – monkut Jun 09 '20 at 05:01
  • I gave up on trying to use `override_settings` and ended up creating an environmentvariable with test env specific settings. Not ideal, but I was able to confirm that a 401 is returned on expire this way. – monkut Jun 09 '20 at 05:22
  • I guessed so. The plugin must take the settings and 'instantiate' itself and boot time, so after that it does not read the settings again and it's useless to override them. Glad it worked. Gonna post the answer for future readers – gmc Jun 09 '20 at 09:34

2 Answers2

2

The plugin must take the settings and instantiate its objects at boot time. At runtime, it does not read the settings again, so it is useless to override them.

As you did, the proper way to test this feature is to have a sepparate settings module for testing and put the plugin config there.

gmc
  • 3,910
  • 2
  • 31
  • 44
0

The above solution works fine, but testing expiry will lead to other tests failing that uses token, because all of them will be expired.

you can follow this solution too Unit Testing JWT token exipiration: Django REST

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 17 '22 at 12:14
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/30837262) – Simas Joneliunas Jan 20 '22 at 07:08