-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ft(JWT Authentication): Implement JWT Authentication
- Add a property function to enable returning of a jwt when user is created - Call property function to return a jwt with the user details during sign up and login - Add an overiding class for authentication of headers and token - Fix failing tests - Add tests to test for the feature implemented - Implement feedback [finishes #165305753]
- Loading branch information
Issa Maina
authored and
Issa Maina
committed
Apr 30, 2019
1 parent
df2b233
commit 5046555
Showing
9 changed files
with
227 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,65 @@ | ||
# import jwt | ||
# | ||
# from django.conf import settings | ||
# | ||
# from rest_framework import authentication, exceptions | ||
# | ||
# from .models import User | ||
import jwt | ||
from django.conf import settings | ||
from rest_framework import authentication, exceptions | ||
from .models import User | ||
|
||
"""Configure JWT Here""" | ||
|
||
|
||
class JWTAuthentication(authentication.BaseAuthentication): | ||
""" | ||
This class will handle users' authentication | ||
""" | ||
|
||
def authenticate(self, request): | ||
""" | ||
This method will authenticate the authorization headers provided. | ||
It is called regardless of whether the endpoint requires | ||
authentication. | ||
""" | ||
prefix = 'Bearer' | ||
header = authentication.get_authorization_header(request).split() | ||
|
||
if not header: | ||
return None | ||
|
||
if len(header) < 2: | ||
resp = 'The authorization header provided is invalid!' | ||
raise exceptions.AuthenticationFailed(resp) | ||
|
||
if header[0].decode('utf-8') != prefix: | ||
resp = 'Please use a Bearer token!' | ||
raise exceptions.AuthenticationFailed(resp) | ||
|
||
token = header[1].decode('utf-8') | ||
|
||
return self.authenticate_token(request, token) | ||
|
||
def authenticate_token(self, request, token): | ||
""" | ||
This method will authenticate the provided token | ||
""" | ||
try: | ||
payload = jwt.api_jwt.decode( | ||
token, settings.SECRET_KEY, algorithms='HS256') | ||
except jwt.api_jwt.DecodeError: | ||
resp = 'Invalid Token. The token provided cannot be decoded!' | ||
raise exceptions.AuthenticationFailed(resp) | ||
except jwt.api_jwt.ExpiredSignatureError: | ||
resp = 'The token used has expired. Please authenticate again!' | ||
raise exceptions.AuthenticationFailed(resp) | ||
|
||
username = payload['user_data']['username'] | ||
email = payload['user_data']['email'] | ||
|
||
try: | ||
user = User.objects.get(email=email, username=username) | ||
except User.DoesNotExist: | ||
resp = 'No user was found from the provided token!' | ||
raise exceptions.AuthenticationFailed(resp) | ||
|
||
if not user.is_active: | ||
resp = 'Your account is not active!' | ||
raise exceptions.AuthenticationFailed(resp) | ||
|
||
return user, token |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
authors/apps/authentication/tests/test_jwt_authentication.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import os | ||
from authors.apps.authentication.tests.basetests import BaseTest | ||
from rest_framework import status | ||
|
||
class TestTokenAuthentication(BaseTest): | ||
""" | ||
This class handles the testing of JWTAuthentication | ||
""" | ||
|
||
def test_get_jwt_after_login(self): | ||
self.signup_user('issa', 'issamwangi@gmail.com', 'Maina9176') | ||
response = self.login_user('issamwangi@gmail.com', 'Maina9176') | ||
self.assertIn('token', str(response.data)) | ||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
|
||
def test_get_jwt_after_signup(self): | ||
response = self.signup_user( | ||
'issa', 'issamwangi@gmail.com', 'Maina9176') | ||
self.assertIn('token', str(response.data)) | ||
self.assertEqual(response.status_code, status.HTTP_201_CREATED) | ||
|
||
def test_invalid_authorization_header(self): | ||
token = self.generate_jwt_token('issamwangi@gmail.com', 'issa') | ||
self.client.credentials( | ||
HTTP_AUTHORIZATION=token) | ||
response = self.client.get(self.get_url) | ||
self.assertEqual( | ||
response.data['detail'], | ||
'The authorization header provided is invalid!') | ||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) | ||
|
||
def test_invalid_header_prefix(self): | ||
token = self.generate_jwt_token('issamwangi@gmail.com', 'issa') | ||
self.client.credentials( | ||
HTTP_AUTHORIZATION='Invalid ' + token) | ||
response = self.client.get(self.get_url) | ||
self.assertEqual( | ||
response.data['detail'], | ||
'Please use a Bearer token!') | ||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) | ||
|
||
def test_invalid_token(self): | ||
token = 'sxdcfvgbhjklrty567dfg' | ||
self.client.credentials( | ||
HTTP_AUTHORIZATION='Bearer ' + token) | ||
response = self.client.get(self.get_url) | ||
self.assertEqual( | ||
response.data['detail'], | ||
'Invalid Token. The token provided cannot be decoded!') | ||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) | ||
|
||
def test_inactive_user(self): | ||
token = self.generate_jwt_token('ian@gmail.com', 'ian') | ||
self.client.credentials( | ||
HTTP_AUTHORIZATION='Bearer ' + token) | ||
response = self.client.get(self.get_url) | ||
self.assertEqual( | ||
response.data['detail'], | ||
'Your account is not active!') | ||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) | ||
|
||
def test_user_not_found_from_token(self): | ||
token = self.generate_jwt_token('ianmwangi@gmail.com', 'ianmaina') | ||
self.client.credentials( | ||
HTTP_AUTHORIZATION='Bearer ' + token) | ||
response = self.client.get(self.get_url) | ||
self.assertEqual( | ||
response.data['detail'], | ||
'No user was found from the provided token!') | ||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) | ||
|
||
def test_expired_jwt_token(self): | ||
token = os.getenv('EXPIREDTOKEN') | ||
self.client.credentials( | ||
HTTP_AUTHORIZATION='Bearer ' + token) | ||
response = self.client.get(self.get_url) | ||
self.assertEqual( | ||
response.data['detail'], | ||
'The token used has expired. Please authenticate again!') | ||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) |
Oops, something went wrong.