Skip to content

Commit

Permalink
Fix simplejwt integration, add tests and update example project (#78)
Browse files Browse the repository at this point in the history
Fix simplejwt integration, add tests and update example project
  • Loading branch information
st4lk committed Mar 11, 2019
1 parent e3770aa commit 9527b88
Show file tree
Hide file tree
Showing 12 changed files with 369 additions and 65 deletions.
6 changes: 5 additions & 1 deletion example_project/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@
url(r'^session/$', views.HomeSessionView.as_view(), name='home_session'),
url(r'^token/$', views.HomeTokenView.as_view(), name='home_token'),
url(r'^jwt/$', views.HomeJWTView.as_view(), name='home_jwt'),
url(r'^jwt-old/$', views.HomeJWTOldView.as_view(), name='home_jwt_old'),
url(r'^knox/$', views.HomeKnoxView.as_view(), name='home_knox'),

url(r'^api/login/', include('rest_social_auth.urls_session')),
url(r'^api/login/', include('rest_social_auth.urls_token')),
url(r'^api/login/', include('rest_social_auth.urls_simplejwt_pair')),
url(r'^api/login/', include('rest_social_auth.urls_simplejwt_sliding')),
url(r'^api/login/', include('rest_social_auth.urls_jwt')),
url(r'^api/login/', include('rest_social_auth.urls_knox')),

url(r'^api/logout/session/$', views.LogoutSessionView.as_view(), name='logout_session'),
url(r'^api/user/session/', views.UserSessionDetailView.as_view(), name="current_user_session"),
url(r'^api/user/token/', views.UserTokenDetailView.as_view(), name="current_user_token"),
url(r'^api/user/jwt/', views.UserJWTDetailView.as_view(), name="current_user_jwt"),
url(r'^api/user/jwt/', views.UserSimpleJWTDetailView.as_view(), name="current_user_jwt"),
url(r'^api/user/jwt-old/', views.UserJWTOldDetailView.as_view(), name="current_user_jwt_old"),
url(r'^api/user/knox/', views.UserKnoxDetailView.as_view(), name="current_user_knox"),
]
if django.VERSION >= (2, 0, 0):
Expand Down
1 change: 1 addition & 0 deletions example_project/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ <h1>Django-rest-framework and OAuth example</h1>
<li><a href="{% url 'home_token' %}">Token auth</a></li>
<li><a href="{% url 'home_jwt' %}">JWT auth</a></li>
<li><a href="{% url 'home_knox' %}">Knox auth</a></li>
<li><a href="{% url 'home_jwt_old' %}">JWT old auth (deprecated)</a></li>
</ul>

{% block content %}
Expand Down
17 changes: 10 additions & 7 deletions example_project/templates/home_jwt.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
{% load staticfiles %}

{% block content %}
<h3>Note! <a href="http://getblimp.github.io/django-rest-framework-jwt/">djangorestframework-jwt</a> must be installed for this method</h3>
<h3>Note! <a href="https://github.com/davesque/django-rest-framework-simplejwt">django-rest-framework-simplejwt</a> must be installed for this method</h3>
<div ng-app="JWTApp">
<div ng-controller="LoginJWTCtrl as ctrl">
<div>
<h4>rest_framework_jwt.authentication.JSONWebTokenAuthentication</h4>
<h4>rest_framework_simplejwt.authentication.JWTAuthentication</h4>
<div><button ng-click="authenticate('facebook')">Login facebook (OAuth 2.0)</button></div>
<div><button ng-click="authenticate('google')">Login google (OAuth 2.0)</button></div>
<div><button ng-click="authenticate('twitter')">Login twitter (OAuth 1.0)*</button></div>
Expand All @@ -15,6 +15,9 @@ <h4>rest_framework_jwt.authentication.JSONWebTokenAuthentication</h4>
<div>
(*) Currently in case of OAuth 1.0 you still need to have session enabled, as python-social-auth will store some data in session between requests. Maybe in future versions of rest-social-auth this will be solved.
</div>
<div>
(**) In this example <a href="https://github.com/davesque/django-rest-framework-simplejwt#token-types">pair token</a> is used. With simplejwt you can use <a href="https://github.com/davesque/django-rest-framework-simplejwt#sliding-tokens">sliding token</a> as well.
</div>
<div>
<h2>JWT user data</h2>
<div>
Expand Down Expand Up @@ -71,19 +74,19 @@ <h2>Raw JWT payload</h2>
angular.module('JWTApp', ['satellizer'])
.config(function($authProvider) {
$authProvider.facebook({
url: "{% url 'login_social_jwt_user' provider='facebook' %}",
url: "{% url 'login_social_simplejwt_pair_user' provider='facebook' %}",
clientId: '{{ facebook_key }}'
});
$authProvider.google({
url: "{% url 'login_social_jwt_user' provider='google-oauth2' %}",
url: "{% url 'login_social_simplejwt_pair_user' provider='google-oauth2' %}",
clientId: '{{ googleoauth2_key }}',
redirectUri: window.location.origin + '/'
});
$authProvider.twitter({
url: "{% url 'login_social_jwt_user' provider='twitter' %}",
url: "{% url 'login_social_simplejwt_pair_user' provider='twitter' %}",
});
$authProvider.authToken = 'JWT';
$authProvider.tokenPrefix = 'satellizer_jwt'; // to not collide with regular token auth
$authProvider.tokenPrefix = 'satellizer_simple_jwt'; // to not collide with regular token auth
}).controller('LoginJWTCtrl', function($scope, $auth, $http) {
self = this;

Expand All @@ -103,7 +106,7 @@ <h2>Raw JWT payload</h2>
set_user(response);
self.jwtPayload = $auth.getPayload();
}).catch(function(data) {
var err_msg = "Something went wrong, maybe you haven't installed 'djangorestframework-jwt'?";
var err_msg = "Something went wrong, maybe you haven't installed 'django-rest-framework-simplejwt'?";
console.log(data)
console.log(err_msg);
alert(err_msg);
Expand Down
122 changes: 122 additions & 0 deletions example_project/templates/home_jwt_old.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{% extends "base.html" %}
{% load staticfiles %}

{% block content %}
<h1>rest_framework_jwt is deprecated and will be dropped in next version! Use <a href="https://github.com/davesque/django-rest-framework-simplejwt">rest_framework_simplejwt</a> instead!</h1>
<h3>Note! <a href="http://getblimp.github.io/django-rest-framework-jwt/">djangorestframework-jwt</a> must be installed for this method</h3>
<div ng-app="JWTApp">
<div ng-controller="LoginJWTCtrl as ctrl">
<div>
<h4>rest_framework_jwt.authentication.JSONWebTokenAuthentication</h4>
<div><button ng-click="authenticate('facebook')">Login facebook (OAuth 2.0)</button></div>
<div><button ng-click="authenticate('google')">Login google (OAuth 2.0)</button></div>
<div><button ng-click="authenticate('twitter')">Login twitter (OAuth 1.0)*</button></div>
<div><button ng-click="logout()">Logout jwt</button></div>
</div>
<div>
(*) Currently in case of OAuth 1.0 you still need to have session enabled, as python-social-auth will store some data in session between requests. Maybe in future versions of rest-social-auth this will be solved.
</div>
<div>
<h2>JWT user data</h2>
<div>
{% verbatim %}
<img ng-src='{{ctrl.user.thumb}}' />
{% endverbatim %}
</div>
<div>
<span>First name:</span>
<span ng-bind="ctrl.user.first_name"></span>
</div>
<div>
<span>Last name:</span>
<span ng-bind="ctrl.user.last_name"></span>
</div>
<div>
<span>Email:</span>
<span ng-bind="ctrl.user.email"></span>
</div>
<h2>Raw JWT payload</h2>
<div>
<pre>{% verbatim %}{{ ctrl.jwtPayload | json }}{% endverbatim %}</pre>
</div>
</div>
</div>
</div>

{% endblock content %}

{% block scripts %}
{{ block.super }}
<script type="text/javascript">

function set_user(response){
var source;
if (response){
source = response.data;
} else {
source = {
'username': null,
'first_name': null,
'last_name': null,
'email': null,
'social_thumb': '{% static "anonymous.png" %}'
};
}
self.user.username = source.username;
self.user.first_name = source.first_name;
self.user.last_name = source.last_name;
self.user.email = source.email;
self.user.thumb = source.social_thumb;
};

angular.module('JWTApp', ['satellizer'])
.config(function($authProvider) {
$authProvider.facebook({
url: "{% url 'login_social_jwt_user' provider='facebook' %}",
clientId: '{{ facebook_key }}'
});
$authProvider.google({
url: "{% url 'login_social_jwt_user' provider='google-oauth2' %}",
clientId: '{{ googleoauth2_key }}',
redirectUri: window.location.origin + '/'
});
$authProvider.twitter({
url: "{% url 'login_social_jwt_user' provider='twitter' %}",
});
$authProvider.authToken = 'JWT';
$authProvider.tokenPrefix = 'satellizer_jwt'; // to not collide with regular token auth
}).controller('LoginJWTCtrl', function($scope, $auth, $http) {
self = this;

self.user = {};
set_user();
if ($auth.getToken()){
$http.get('{% url "current_user_jwt_old" %}').then(function(response){
set_user(response);
});
}

self.jwtPayload = $auth.getPayload();

$scope.authenticate = function(provider) {
$auth.authenticate(provider).then(function(response){
$auth.setToken(response.data.token);
set_user(response);
self.jwtPayload = $auth.getPayload();
}).catch(function(data) {
var err_msg = "Something went wrong, maybe you haven't installed 'djangorestframework-jwt'?";
console.log(data)
console.log(err_msg);
alert(err_msg);
});
};

$scope.logout = function(){
$auth.removeToken();
set_user();
self.jwtPayload = $auth.getPayload();
};
});

</script>
{% endblock scripts %}
12 changes: 10 additions & 2 deletions example_project/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from rest_framework.response import Response
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
from rest_social_auth.serializers import UserSerializer
from rest_social_auth.views import JWTAuthMixin, KnoxAuthMixin
from rest_social_auth.views import JWTAuthMixin, KnoxAuthMixin, SimpleJWTAuthMixin


class HomeSessionView(TemplateView):
Expand All @@ -27,6 +27,10 @@ class HomeJWTView(TemplateView):
template_name = 'home_jwt.html'


class HomeJWTOldView(TemplateView):
template_name = 'home_jwt_old.html'


class HomeKnoxView(TemplateView):
template_name = 'home_knox.html'

Expand Down Expand Up @@ -55,7 +59,11 @@ class UserTokenDetailView(BaseDetailView):
authentication_classes = (TokenAuthentication, )


class UserJWTDetailView(JWTAuthMixin, BaseDetailView):
class UserJWTOldDetailView(JWTAuthMixin, BaseDetailView):
pass


class UserSimpleJWTDetailView(SimpleJWTAuthMixin, BaseDetailView):
pass


Expand Down
2 changes: 1 addition & 1 deletion requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pytest-django==3.4.4
pytest-django==3.4.8
httpretty==0.9.6
unittest2==1.0.1
mock==1.3.0
102 changes: 61 additions & 41 deletions rest_social_auth/serializers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import logging
from importlib import import_module
import warnings

from django.contrib.auth import get_user_model
from rest_framework import serializers
from rest_framework.authtoken.models import Token
from django.contrib.auth import get_user_model


l = logging.getLogger(__name__)


class OAuth2InputSerializer(serializers.Serializer):
Expand All @@ -26,8 +24,10 @@ class UserSerializer(serializers.ModelSerializer):

class Meta:
model = get_user_model()
exclude = ('is_staff', 'is_active', 'date_joined', 'password',
'last_login', 'user_permissions', 'groups', 'is_superuser',)
exclude = (
'is_staff', 'is_active', 'date_joined', 'password', 'last_login', 'user_permissions',
'groups', 'is_superuser',
)


class TokenSerializer(serializers.Serializer):
Expand Down Expand Up @@ -90,49 +90,69 @@ class UserKnoxSerializer(KnoxSerializer, UserSerializer):


class SimpleJWTBaseSerializer(serializers.Serializer):
def __init__(self, *args, **kwargs):
super(SimpleJWTBaseSerializer, self).__init__(*args, **kwargs)
if self.instance is not None:
self.token = self.get_token(self.instance)

@classmethod
def get_token_instance(cls, user):
raise NotImplementedError('Must implement `get_token` method for `SimpleJWTBaseSerializer` subclasses')
jwt_token_class_name = None

def get_token_instance(self):
if not hasattr(self, '_jwt_token_instance'):
if self.jwt_token_class_name is None:
raise NotImplementedError('Must specify `jwt_token_class_name` property')
try:
tokens_module = import_module('rest_framework_simplejwt.tokens')
except ImportError:
warnings.warn(
'djangorestframework_simplejwt must be installed for JWT authentication',
ImportWarning,
)
raise
token_class = getattr(tokens_module, self.jwt_token_class_name)
user = self.instance
self._jwt_token_instance = token_class.for_user(user)
for key, value in self.get_token_payload(user).items():
self._jwt_token_instance[key] = value
return self._jwt_token_instance

def get_token_payload(self, user):
"""
Payload defined here will be added to default mandatory payload.
Receive User instance in argument, returns dict.
"""
return {}


class SimpleJWTObtainPairSerializer(SimpleJWTBaseSerializer):
access = serializers.SerializerMethodField()
class SimpleJWTPairSerializer(SimpleJWTBaseSerializer):
token = serializers.SerializerMethodField()
refresh = serializers.SerializerMethodField()

@classmethod
def get_token_instance(cls, user):
try:
from rest_framework_simplejwt.tokens import RefreshToken
except ImportError:
warnings.warn('djangorestframework_simplejwt must be installed for JWT authentication',
ImportWarning)
raise
return RefreshToken.for_user(user)
jwt_token_class_name = 'RefreshToken'

def get_token(self, obj):
return str(self.get_token_instance().access_token)

def get_refresh(self, obj):
return str(self.get_token_instance())

def get_access(self, _obj):
return str(self.token.access_token)

def get_refresh(self, _obj):
return str(self.token)
class UserSimplePairJWTSerializer(SimpleJWTPairSerializer, UserSerializer):

def get_token_payload(self, user):
payload = dict(UserSerializer(user).data)
payload.pop('id', None)
return payload

class SimpleJWTObtainSlidingSerializer(SimpleJWTBaseSerializer):

class SimpleJWTSlidingSerializer(SimpleJWTBaseSerializer):
token = serializers.SerializerMethodField()

@classmethod
def get_token_instance(cls, user):
try:
from rest_framework_simplejwt.tokens import SlidingToken
except ImportError:
warnings.warn('djangorestframework_simplejwt must be installed for JWT authentication',
ImportWarning)
raise
return SlidingToken.for_user(user)
jwt_token_class_name = 'SlidingToken'

def get_token(self, obj):
return str(self.get_token_instance())


class UserSimpleJWTSlidingSerializer(SimpleJWTSlidingSerializer, UserSerializer):

def get_token(self, _obj):
return str(self.token)
def get_token_payload(self, user):
payload = dict(UserSerializer(user).data)
payload.pop('id', None)
return payload
8 changes: 7 additions & 1 deletion rest_social_auth/urls_simplejwt_pair.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@


urlpatterns = (
# returns token only
url(r'^social/jwt-pair/(?:(?P<provider>[a-zA-Z0-9_-]+)/?)?$',
views.SocialSimpleJWTPairOnlyAuthView.as_view(),
name='login_social_simplejwt_pair'),)
name='login_social_simplejwt_pair'),
# returns token + user_data
url(r'^social/jwt-pair-user/(?:(?P<provider>[a-zA-Z0-9_-]+)/?)?$',
views.SocialSimpleJWTPairUserAuthView.as_view(),
name='login_social_simplejwt_pair_user'),
)
Loading

0 comments on commit 9527b88

Please sign in to comment.