Skip to content
This repository was archived by the owner on May 26, 2020. It is now read-only.

Commit 2b15582

Browse files
committed
Abstracted out logic from the refresh serializer to make a verification serializer. Added a verification view. Added verification tests.
1 parent d2c3e5a commit 2b15582

File tree

3 files changed

+146
-38
lines changed

3 files changed

+146
-38
lines changed

rest_framework_jwt/serializers.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,17 @@ def validate(self, attrs):
8181
raise serializers.ValidationError(msg)
8282

8383

84-
class RefreshJSONWebTokenSerializer(Serializer):
84+
class VerificationBaseSerializer(Serializer):
8585
"""
86-
Check an access token
86+
Abstract serializer used for verifying and refreshing JWTs.
8787
"""
8888
token = serializers.CharField()
8989

9090
def validate(self, attrs):
91-
User = utils.get_user_model()
92-
token = attrs['token']
91+
msg = _('Please define a validate method.')
92+
raise NotImplementedError(msg)
9393

94+
def _check_payload(self, token):
9495
# Check payload valid (based off of JSONWebTokenAuthentication,
9596
# may want to refactor)
9697
try:
@@ -102,6 +103,10 @@ def validate(self, attrs):
102103
msg = _('Error decoding signature.')
103104
raise serializers.ValidationError(msg)
104105

106+
return payload
107+
108+
def _check_user(self, payload):
109+
User = utils.get_user_model()
105110
# Make sure user exists (may want to refactor this)
106111
try:
107112
user_id = jwt_get_user_id_from_payload(payload)
@@ -115,6 +120,38 @@ def validate(self, attrs):
115120
msg = _("User doesn't exist.")
116121
raise serializers.ValidationError(msg)
117122

123+
return user
124+
125+
126+
class VerifyJSONWebTokenSerializer(VerificationBaseSerializer):
127+
"""
128+
Check the veracity of an access token.
129+
"""
130+
131+
def validate(self, attrs):
132+
token = attrs['token']
133+
134+
payload = self._check_payload(token=token)
135+
user = self._check_user(payload=payload)
136+
137+
new_payload = jwt_payload_handler(user)
138+
139+
return {
140+
'token': jwt_encode_handler(new_payload),
141+
'user': user
142+
}
143+
144+
145+
class RefreshJSONWebTokenSerializer(VerificationBaseSerializer):
146+
"""
147+
Refresh an access token.
148+
"""
149+
150+
def validate(self, attrs):
151+
token = attrs['token']
152+
153+
payload = self._check_payload(token=token)
154+
user = self._check_user(payload=payload)
118155
# Get and check 'orig_iat'
119156
orig_iat = payload.get('orig_iat')
120157

rest_framework_jwt/views.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
from rest_framework_jwt.settings import api_settings
88

9-
from .serializers import JSONWebTokenSerializer, RefreshJSONWebTokenSerializer
9+
from .serializers import (
10+
JSONWebTokenSerializer, RefreshJSONWebTokenSerializer,
11+
VerifyJSONWebTokenSerializer
12+
)
1013

1114
jwt_response_payload_handler = api_settings.JWT_RESPONSE_PAYLOAD_HANDLER
1215

@@ -37,20 +40,17 @@ def post(self, request):
3740
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
3841

3942

40-
class RefreshJSONWebToken(APIView):
43+
class VerifyJSONWebToken(APIView):
4144
"""
42-
API View that returns a refreshed token (with new expiration) based on
43-
existing token
44-
45-
If 'orig_iat' field (original issued-at-time) is found, will first check
46-
if it's within expiration window, then copy it to the new token
45+
API View that checks the veracity of a token, returning the token if it
46+
is valid.
4747
"""
4848
throttle_classes = ()
4949
permission_classes = ()
5050
authentication_classes = ()
5151
parser_classes = (parsers.FormParser, parsers.JSONParser,)
5252
renderer_classes = (renderers.JSONRenderer,)
53-
serializer_class = RefreshJSONWebTokenSerializer
53+
serializer_class = VerifyJSONWebTokenSerializer
5454

5555
def post(self, request):
5656
serializer = self.serializer_class(data=request.DATA)
@@ -65,5 +65,17 @@ def post(self, request):
6565
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
6666

6767

68+
class RefreshJSONWebToken(VerifyJSONWebToken):
69+
"""
70+
API View that returns a refreshed token (with new expiration) based on
71+
existing token
72+
73+
If 'orig_iat' field (original issued-at-time) is found, will first check
74+
if it's within expiration window, then copy it to the new token
75+
"""
76+
serializer_class = RefreshJSONWebTokenSerializer
77+
78+
6879
obtain_jwt_token = ObtainJSONWebToken.as_view()
6980
refresh_jwt_token = RefreshJSONWebToken.as_view()
81+
verify_jwt_token = VerifyJSONWebToken.as_view()

tests/test_views.py

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
'',
2626
(r'^auth-token/$', 'rest_framework_jwt.views.obtain_jwt_token'),
2727
(r'^auth-token-refresh/$', 'rest_framework_jwt.views.refresh_jwt_token'),
28+
(r'^auth-token-verify/$', 'rest_framework_jwt.views.verify_jwt_token'),
29+
2830
)
2931

3032
orig_datetime = datetime
@@ -204,20 +206,18 @@ def test_jwt_login_json_bad_creds(self):
204206
self.assertEqual(response.status_code, 400)
205207

206208

207-
class RefreshJSONWebTokenTests(BaseTestCase):
208-
urls = 'tests.test_views'
209-
210-
def setUp(self):
211-
super(RefreshJSONWebTokenTests, self).setUp()
212-
api_settings.JWT_ALLOW_REFRESH = True
209+
class TokenTestCase(BaseTestCase):
210+
"""
211+
Handlers for getting tokens from the API, or creating arbitrary ones.
212+
"""
213213

214214
def get_token(self):
215215
client = APIClient(enforce_csrf_checks=True)
216216
response = client.post('/auth-token/', self.data, format='json')
217217
return response.data['token']
218218

219219
def create_token(self, user, exp=None, orig_iat=None):
220-
payload = utils.jwt_payload_handler(self.user)
220+
payload = utils.jwt_payload_handler(user)
221221
if exp:
222222
payload['exp'] = exp
223223

@@ -227,6 +227,84 @@ def create_token(self, user, exp=None, orig_iat=None):
227227
token = utils.jwt_encode_handler(payload)
228228
return token
229229

230+
231+
class VerifyJSONWebTokenTests(TokenTestCase):
232+
233+
def test_verify_jwt(self):
234+
"""
235+
Test that a valid, non-expired token will return a 200 response
236+
and itself when passed to the validation endpoint.
237+
"""
238+
client = APIClient(enforce_csrf_checks=True)
239+
240+
orig_token = self.get_token()
241+
242+
# Now try to get a refreshed token
243+
response = client.post('/auth-token-verify/', {'token': orig_token},
244+
format='json')
245+
self.assertEqual(response.status_code, status.HTTP_200_OK)
246+
247+
self.assertEqual(response.data['token'], orig_token)
248+
249+
def test_verify_jwt_fails_with_expired_token(self):
250+
"""
251+
Test that an expired token will fail with the correct error.
252+
"""
253+
client = APIClient(enforce_csrf_checks=True)
254+
255+
# Make an expired token..
256+
token = self.create_token(
257+
self.user,
258+
exp=datetime.utcnow() - timedelta(seconds=5),
259+
orig_iat=datetime.utcnow() - timedelta(hours=1)
260+
)
261+
262+
response = client.post('/auth-token-verify/', {'token': token},
263+
format='json')
264+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
265+
self.assertRegexpMatches(response.data['non_field_errors'][0],
266+
'Signature has expired')
267+
268+
def test_verify_jwt_fails_with_bad_token(self):
269+
"""
270+
Test that an invalid token will fail with the correct error.
271+
"""
272+
client = APIClient(enforce_csrf_checks=True)
273+
274+
token = "i am not a correctly formed token"
275+
276+
response = client.post('/auth-token-verify/', {'token': token},
277+
format='json')
278+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
279+
self.assertRegexpMatches(response.data['non_field_errors'][0],
280+
'Error decoding signature')
281+
282+
def test_verify_jwt_fails_with_missing_user(self):
283+
"""
284+
Test that an invalid token will fail with a user that does not exist.
285+
"""
286+
client = APIClient(enforce_csrf_checks=True)
287+
288+
user = User.objects.create_user(
289+
email='[email protected]', username='jsmith', password='password')
290+
291+
token = self.create_token(user)
292+
# Delete the user used to make the token
293+
user.delete()
294+
295+
response = client.post('/auth-token-verify/', {'token': token},
296+
format='json')
297+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
298+
self.assertRegexpMatches(response.data['non_field_errors'][0],
299+
"User doesn't exist")
300+
301+
302+
class RefreshJSONWebTokenTests(TokenTestCase):
303+
304+
def setUp(self):
305+
super(RefreshJSONWebTokenTests, self).setUp()
306+
api_settings.JWT_ALLOW_REFRESH = True
307+
230308
def test_refresh_jwt(self):
231309
"""
232310
Test getting a refreshed token from original token works
@@ -257,25 +335,6 @@ def test_refresh_jwt(self):
257335
self.assertEquals(new_token_decoded['orig_iat'], orig_iat)
258336
self.assertGreater(new_token_decoded['exp'], orig_token_decoded['exp'])
259337

260-
def test_refresh_jwt_fails_with_expired_token(self):
261-
"""
262-
Test that using an expired token to refresh won't work
263-
"""
264-
client = APIClient(enforce_csrf_checks=True)
265-
266-
# Make an expired token..
267-
token = self.create_token(
268-
self.user,
269-
exp=datetime.utcnow() - timedelta(seconds=5),
270-
orig_iat=datetime.utcnow() - timedelta(hours=1)
271-
)
272-
273-
response = client.post('/auth-token-refresh/', {'token': token},
274-
format='json')
275-
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
276-
self.assertRegexpMatches(response.data['non_field_errors'][0],
277-
'Signature has expired')
278-
279338
def test_refresh_jwt_after_refresh_expiration(self):
280339
"""
281340
Test that token can't be refreshed after token refresh limit

0 commit comments

Comments
 (0)