Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for JWT authentication #25

Merged
merged 10 commits into from
Jun 11, 2018
12 changes: 12 additions & 0 deletions devRequirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
certifi==2018.4.16
chardet==3.0.4
Django==1.10.1
django-cors-headers==1.3.1
django-filter==1.0.1
djangorestframework==3.4.7
djangorestframework-jwt==1.11.0
DukeDSClient==1.0.1
funcsigs==1.0.2
future==0.16.0
idna==2.5
mock==2.0.0
oauthlib==2.1.0
pbr==4.0.3
PyJWT==1.5.2
pytz==2018.4
PyYAML==3.12
requests==2.18.1
requests-oauthlib==0.8.0
six==1.11.0
urllib3==1.21.1
30 changes: 30 additions & 0 deletions gcb_web_auth/jwt_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import authentication
from rest_framework_jwt.settings import api_settings
from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer

# Based on http://getblimp.github.io/django-rest-framework-jwt/#creating-a-new-token-manually

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


class SessionObtainJSONWebTokenAPIView(APIView):
"""
Allow session-authenticated users to get a JSON web token
"""
permission_classes = (IsAuthenticated,)
authentication_classes = (authentication.SessionAuthentication,)

def post(self, request, format=None):
payload = jwt_payload_handler(request.user)
token = jwt_encode_handler(payload)
# This returned token is already json encoded, use the verifyJSONWebTokenSerializer to serialize it
serializer = VerifyJSONWebTokenSerializer(data={'token': token})
serializer.is_valid()
return Response(serializer.data)


session_jwt_token = SessionObtainJSONWebTokenAPIView.as_view()
30 changes: 30 additions & 0 deletions gcb_web_auth/tests_jwt_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from rest_framework.test import APITestCase
from django.core.urlresolvers import reverse
from django.contrib.auth import get_user_model
from requests.auth import HTTPBasicAuth
from django.conf import settings


class JWTViewsTestCase(APITestCase):

def test_fails_when_logged_out(self):
self.client.logout()
response = self.client.post(reverse('auth-api-token-session'), {})
self.assertEqual(response.status_code, 403)
self.assertEqual(response.data["detail"], "Authentication credentials were not provided.")

def test_fails_with_non_session_authentication(self):
# Will try HTTP Basic Authentication, make sure that's elected in the settings
self.assertIn('rest_framework.authentication.BasicAuthentication', settings.REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'])
user = get_user_model().objects.create(username='user', password='pass')
self.client.auth = HTTPBasicAuth('user', 'pass')
response = self.client.post(reverse('auth-api-token-session'), {})
self.assertEqual(response.status_code, 403)
self.assertEqual(response.data["detail"], "Authentication credentials were not provided.")

def test_succeeds_with_session_authentication(self):
get_user_model().objects.create_user(username='user', password='pass')
self.client.login(username='user', password='pass')
response = self.client.post(reverse('auth-api-token-session'), {})
self.assertEqual(response.status_code, 200)
self.assertIn('token', response.data)
7 changes: 7 additions & 0 deletions gcb_web_auth/urls.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
from django.conf.urls import url
from django.views.generic.base import TemplateView
from django.contrib.auth import views as auth_views
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token, verify_jwt_token
from jwt_views import session_jwt_token
from . import views

urlpatterns = [
url(r'^authorize/$', views.authorize, name='auth-authorize'),
url(r'^code_callback/$', views.authorize_callback, name='auth-callback'),
url(r'^home/$', views.home, name='auth-home'),
url(r'^unconfigured/$', TemplateView.as_view(template_name='gcb_web_auth/unconfigured.html'), name='auth-unconfigured'),
# JSON Web Token endpoints
url(r'^api-token-auth/', obtain_jwt_token, name='auth-api-token-auth'),
url(r'^api-token-refresh/', refresh_jwt_token, name='auth-api-token-refresh'),
url(r'^api-token-verify/', verify_jwt_token, name='auth-api-token-verify'),
url(r'^api-token-session/', session_jwt_token, name='auth-api-token-session'),
]
19 changes: 19 additions & 0 deletions sample_project/sample_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"""

import os
import datetime

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Expand All @@ -37,6 +38,7 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'gcb_web_auth',
'todo',
]
Expand Down Expand Up @@ -125,3 +127,20 @@
# https://docs.djangoproject.com/en/1.10/howto/static-files/

STATIC_URL = '/static/'

REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
# Configure djangorestframework-jwt
JWT_AUTH = {
# Allow token refresh
'JWT_ALLOW_REFRESH': True,
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=7200),
}
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@

setup(
name='gcb-web-auth',
version='0.10',
version='0.20',
packages=find_packages(),
install_requires=[
'DukeDSClient==1.0.1',
'PyJWT==1.5.2',
'requests==2.18.1',
'requests-oauthlib==0.8.0',
'djangorestframework-jwt==1.11.0',
],
include_package_data=True,
license='MIT License',
Expand Down