Skip to content

Commit 19e234d

Browse files
committed
* Added support for REST_USE_JWT
* Added JWTSerializer * Added JWT encoding support, based on django-rest-framework-jwt * Tests for JWT authentication
1 parent 4a56a9e commit 19e234d

File tree

11 files changed

+157
-20
lines changed

11 files changed

+157
-20
lines changed

docs/configuration.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ Configuration
2929
...
3030
}
3131
32-
3332
- **REST_SESSION_LOGIN** - Enable session login in Login API view (default: True)
3433

3534

3635
- **OLD_PASSWORD_FIELD_ENABLED** - set it to True if you want to have old password verification on password change enpoint (default: False)
3736

3837
- **LOGOUT_ON_PASSWORD_CHANGE** - set to False if you want to keep the current user logged in after a password change
38+
39+
- **REST_USE_JWT** - If enabled, this will use `django-rest-framework-jwt <http://getblimp.github.io/django-rest-framework-jwt/>` as a backend, and instead of session based tokens or Social Login keys, it will return a JWT.
40+

rest_auth/app_settings.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from rest_auth.serializers import (
44
TokenSerializer as DefaultTokenSerializer,
5+
JWTSerializer as DefaultJWTSerializer,
56
UserDetailsSerializer as DefaultUserDetailsSerializer,
67
LoginSerializer as DefaultLoginSerializer,
78
PasswordResetSerializer as DefaultPasswordResetSerializer,
@@ -15,6 +16,9 @@
1516
TokenSerializer = import_callable(
1617
serializers.get('TOKEN_SERIALIZER', DefaultTokenSerializer))
1718

19+
JWTSerializer = import_callable(
20+
serializers.get('JWT_SERIALIZER', DefaultJWTSerializer))
21+
1822
UserDetailsSerializer = import_callable(
1923
serializers.get('USER_DETAILS_SERIALIZER', DefaultUserDetailsSerializer)
2024
)

rest_auth/registration/views.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from django.http import HttpRequest
2+
from django.conf import settings
3+
24
from rest_framework.views import APIView
35
from rest_framework.response import Response
46
from rest_framework.permissions import AllowAny
@@ -13,6 +15,8 @@
1315
from rest_auth.registration.serializers import SocialLoginSerializer
1416
from rest_auth.views import LoginView
1517

18+
from rest_auth.utils import jwt_encode
19+
1620

1721
class RegisterView(APIView, SignupView):
1822
"""
@@ -28,7 +32,12 @@ class RegisterView(APIView, SignupView):
2832
permission_classes = (AllowAny,)
2933
allowed_methods = ('POST', 'OPTIONS', 'HEAD')
3034
token_model = Token
31-
serializer_class = TokenSerializer
35+
36+
def get_serializer_class(self):
37+
if getattr(settings, 'REST_USE_JWT', False):
38+
return JWTSerializer
39+
else:
40+
return TokenSerializer
3241

3342
def get(self, *args, **kwargs):
3443
return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED)
@@ -38,9 +47,14 @@ def put(self, *args, **kwargs):
3847

3948
def form_valid(self, form):
4049
self.user = form.save(self.request)
41-
self.token, created = self.token_model.objects.get_or_create(
42-
user=self.user
43-
)
50+
51+
if getattr(settings, 'REST_USE_JWT', False):
52+
self.token = jwt_encode(self.user)
53+
54+
else:
55+
self.token, created = self.token_model.objects.get_or_create(
56+
user=self.user
57+
)
4458
if isinstance(self.request, HttpRequest):
4559
request = self.request
4660
else:
@@ -65,9 +79,19 @@ def post(self, request, *args, **kwargs):
6579
return self.get_response_with_errors()
6680

6781
def get_response(self):
68-
# serializer = self.user_serializer_class(instance=self.user)
69-
serializer = self.serializer_class(instance=self.token,
70-
context={'request': self.request})
82+
serializer_class = self.get_serializer_class()
83+
84+
if getattr(settings, 'REST_USE_JWT', False):
85+
data = {
86+
'user': self.user,
87+
'token': self.token
88+
}
89+
serializer = serializer_class(instance=data,
90+
context={'request': self.request})
91+
else:
92+
serializer = serializer_class(instance=self.token,
93+
context={'request': self.request})
94+
7195
return Response(serializer.data, status=status.HTTP_201_CREATED)
7296

7397
def get_response_with_errors(self):

rest_auth/serializers.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ class Meta:
8787
model = Token
8888
fields = ('key',)
8989

90-
9190
class UserDetailsSerializer(serializers.ModelSerializer):
9291

9392
"""
@@ -98,6 +97,12 @@ class Meta:
9897
fields = ('username', 'email', 'first_name', 'last_name')
9998
read_only_fields = ('email', )
10099

100+
class JWTSerializer(serializers.Serializer):
101+
"""
102+
Serializer for JWT authentication.
103+
"""
104+
token = serializers.CharField()
105+
user = UserDetailsSerializer()
101106

102107
class PasswordResetSerializer(serializers.Serializer):
103108

rest_auth/tests/requirements.pip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
django-allauth>=0.19.1
22
responses>=0.3.0
33
flake8==2.4.0
4+
djangorestframework-jwt>=1.7.2

rest_auth/tests/settings.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@
4545
"allauth.socialaccount.context_processors.socialaccount",
4646
]
4747

48+
REST_FRAMEWORK = {
49+
'DEFAULT_AUTHENTICATION_CLASSES': (
50+
'rest_framework.authentication.SessionAuthentication',
51+
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
52+
)
53+
}
54+
4855
INSTALLED_APPS = [
4956
'django.contrib.admin',
5057
'django.contrib.auth',
@@ -64,7 +71,9 @@
6471
'rest_framework.authtoken',
6572

6673
'rest_auth',
67-
'rest_auth.registration'
74+
'rest_auth.registration',
75+
76+
'rest_framework_jwt'
6877
]
6978

7079
SECRET_KEY = "38dh*skf8sjfhs287dh&^hd8&3hdg*j2&sd"

rest_auth/tests/test_api.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ def test_login(self):
9090
# test empty payload
9191
self.post(self.login_url, data={}, status_code=400)
9292

93+
@override_settings(REST_USE_JWT=True)
94+
def test_login_jwt(self):
95+
payload = {
96+
"username": self.USERNAME,
97+
"password": self.PASS
98+
}
99+
# no users in db so it should throw an error
100+
user = get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
101+
self.post(self.login_url, data=payload, status_code=200)
102+
self.assertEqual('user' in self.response.json.keys(), True)
103+
self.assertEqual('token' in self.response.json.keys(), True)
104+
105+
93106
def test_password_change(self):
94107
login_payload = {
95108
"username": self.USERNAME,
@@ -258,6 +271,23 @@ def test_user_details(self):
258271
self.assertEqual(user.last_name, self.response.json['last_name'])
259272
self.assertEqual(user.email, self.response.json['email'])
260273

274+
@override_settings(REST_USE_JWT=True)
275+
def test_user_details_jwt(self):
276+
user = get_user_model().objects.create_user(self.USERNAME, self.EMAIL, self.PASS)
277+
payload = {
278+
'username': self.USERNAME,
279+
'password': self.PASS
280+
}
281+
self.post(self.login_url, data=payload, status_code=200)
282+
self.token = self.response.json['token']
283+
self.get(self.user_url, status_code=200)
284+
285+
self.patch(self.user_url, data=self.BASIC_USER_DATA, status_code=200)
286+
user = get_user_model().objects.get(pk=user.pk)
287+
self.assertEqual(user.first_name, self.response.json['first_name'])
288+
self.assertEqual(user.last_name, self.response.json['last_name'])
289+
self.assertEqual(user.email, self.response.json['email'])
290+
261291
def test_registration(self):
262292
user_count = get_user_model().objects.all().count()
263293

rest_auth/tests/test_base.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ def send_request(self, request_method, *args, **kwargs):
3737

3838
# check_headers = kwargs.pop('check_headers', True)
3939
if hasattr(self, 'token'):
40-
kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token
40+
if getattr(settings, 'REST_USE_JWT', False):
41+
kwargs['HTTP_AUTHORIZATION'] = 'JWT %s' % self.token
42+
else:
43+
kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token
4144

4245
self.response = request_func(*args, **kwargs)
4346
is_json = bool(

rest_auth/tests/test_social.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,29 @@ def test_edge_case(self):
126126

127127
self.post(self.fb_login_url, data=payload, status_code=200)
128128
self.assertIn('key', self.response.json.keys())
129+
130+
@responses.activate
131+
@override_settings(
132+
REST_USE_JWT=True
133+
)
134+
def test_jwt(self):
135+
resp_body = '{"id":"123123123123","first_name":"John","gender":"male","last_name":"Smith","link":"https:\\/\\/www.facebook.com\\/john.smith","locale":"en_US","name":"John Smith","timezone":2,"updated_time":"2014-08-13T10:14:38+0000","username":"john.smith","verified":true}' # noqa
136+
responses.add(
137+
responses.GET,
138+
self.graph_api_url,
139+
body=resp_body,
140+
status=200,
141+
content_type='application/json'
142+
)
143+
144+
users_count = get_user_model().objects.all().count()
145+
payload = {
146+
'access_token': 'abc123'
147+
}
148+
149+
self.post(self.fb_login_url, data=payload, status_code=200)
150+
self.assertIn('token', self.response.json.keys())
151+
self.assertIn('user', self.response.json.keys())
152+
153+
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
154+

rest_auth/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,15 @@ def import_callable(path_or_callable):
99
assert isinstance(path_or_callable, string_types)
1010
package, attr = path_or_callable.rsplit('.', 1)
1111
return getattr(import_module(package), attr)
12+
13+
def jwt_encode(user):
14+
try:
15+
from rest_framework_jwt.settings import api_settings
16+
except ImportError:
17+
raise ImportError('rest_framework_jwt needs to be installed')
18+
19+
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
20+
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
21+
22+
payload = jwt_payload_handler(user)
23+
return jwt_encode_handler(payload)

0 commit comments

Comments
 (0)