Skip to content

Commit

Permalink
0.1.6 - JWT refactoring including custom django middleware and auth b…
Browse files Browse the repository at this point in the history
…ackend
  • Loading branch information
Tomáš Rychlik committed May 5, 2014
1 parent db6848f commit 872877f
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 21 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Features
- JSONApiResponseMixin providing render_api_response and render_api_error_response methods
- Accept JSON request payload
- JSONApiFormView that can be used for simple JSON APIs using django.forms
- JSON Web token validation (pending to be moved into separate package)
- JSON Web token validation (custom django middleware&auth backend)
- JSONTestClient providing utilities that can be used to test created APIs
- Default JSONEncoder handling Decimal numbers
- Simple jsonify template filter (add 'jsonis' to your application list in settings)
Expand Down
1 change: 1 addition & 0 deletions jsonis/jwt/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from views import JWTAuthorizationMixin
18 changes: 18 additions & 0 deletions jsonis/jwt/auth_backends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend

from .utils import parse_token, JWTParseError

class JWTAuthenticationBackend(ModelBackend):
"""Custom django authentication backend using JWT"""
def authenticate(self, authorization_token=None, **kwargs):
UserModel = get_user_model()
if authorization_token is None:
return
try:
token_data = parse_token(authorization_token)
return UserModel._default_manager.get(pk=token_data['id'])
except JWTParseError:
pass
except UserModel.DoesNotExist:
pass
37 changes: 37 additions & 0 deletions jsonis/jwt/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from django.contrib.auth import authenticate, login, get_user_model
from django.core.exceptions import ImproperlyConfigured

class JWTAuthMiddleware(object):
"""Authentication Middleware that checks for a JSON Web Token in the Authorization header
JWTAuthenticationBackend needs to be added to AUTHENTICATION_BACKENDS as well"""

# Used HTTP Header
header = 'HTTP_AUTHORIZATION'

# Required header prefix
required_auth_prefix = 'Bearer'

def process_request(self, request):
if not hasattr(request, 'user'):
raise ImproperlyConfigured(
"The JWT auth middleware requires the authentication middleware to be installed. Edit your"
" MIDDLEWARE_CLASSES setting to insert 'django.contrib.auth.middleware.AuthenticationMiddleware'"
" before the JWTAuthMiddleware class.")

try:
auth_prefix, auth_token = request.META[self.header].split(' ')
if auth_prefix != self.required_auth_prefix:
raise ValueError

user = authenticate(authorization_token=auth_token)
if user:
request.user = user
login(request, user)

except KeyError:
# There is no self.header
pass
except ValueError:
# Header prefix doesn't match
pass
29 changes: 29 additions & 0 deletions jsonis/jwt/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import jwt

from django.conf import settings

class JWTParseError(Exception):
pass

class JWTDecodeError(JWTParseError):
pass

class JWTExpiredError(JWTParseError):
pass

class JWTNoDataError(JWTDecodeError):
pass

def parse_token(auth_token):
"""Parser given JSON Web Token and returns contained data"""
try:
decoded_token = jwt.decode(auth_token, settings.FIREBASE_SECRET)
except (jwt.DecodeError, ValueError):
raise JWTDecodeError('Decoding of authorization token failed')
except jwt.ExpiredSignature:
raise JWTExpiredError('Expired authorization token')

try:
return decoded_token['d']
except (TypeError, KeyError):
raise JWTNoDataError('No data in authorization token')
28 changes: 11 additions & 17 deletions jsonis/jwt.py → jsonis/jwt/views.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
from __future__ import absolute_import

import jwt

from django.conf import settings
from django.contrib.auth import get_user_model
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt

from .views import JSONApiResponseMixin
from jsonis.views import JSONApiResponseMixin
from .utils import parse_token, JWTParseError


class JWTAuthorizationMixin(JSONApiResponseMixin):
"""JSON API mixin that enables JSON Web Token authorization"""
"""JSON API mixin that enables JSON Web Token authorization
FIXME: Deprecated, move to middleware & auth backend"""

@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated():
Expand All @@ -20,20 +19,15 @@ def dispatch(self, request, *args, **kwargs):
try:
auth_prefix, auth_token = request.META['HTTP_AUTHORIZATION'].split(' ')
if auth_prefix != 'Bearer':
raise ValueError
token_data = jwt.decode(auth_token, settings.FIREBASE_SECRET)
return self.render_api_error_response('Not authenticated - Bad authorization header', status=401)
token_data = parse_token(auth_token)
except KeyError:
return self.render_api_error_response('Not authenticated - Missing authorization header', status=401)
except ValueError:
return self.render_api_error_response('Not authenticated - Bad authorization header', status=401)
except jwt.DecodeError:
return self.render_api_error_response('Not authenticated - Bad authorization header (decode failed)',
status=401)
except jwt.ExpiredSignature:
return self.render_api_error_response('Not authenticated - Expired authorization header', status=401)
except JWTParseError as e:
return self.render_api_error_response('Not authenticated - %s' % e, status=401)

try:
self.user = get_user_model().objects.get(pk=token_data['d']['id'])
self.user = get_user_model().objects.get(pk=token_data['id'])
except (TypeError, KeyError):
return self.render_api_error_response('Not authenticated - Bad authorization header data', status=401)
except get_user_model().DoesNotExist:
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env python

from distutils.core import setup
from setuptools import setup

setup(
name='django-jsonis',
version='0.1.5',
version='0.1.6',
description='Django JSON Utils',
author='Tomas Rychlik',
author_email='[email protected]',
packages=['jsonis', 'jsonis.templatetags'],
packages=['jsonis', 'jsonis.templatetags', 'jsonis.jwt'],
license='MIT',
url='https://github.com/rychlis/django-jsonis',
install_requires=[
Expand Down

0 comments on commit 872877f

Please sign in to comment.