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

Jwt cookie bis #321

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CURRENT_SIGN_SETTING := $(shell git config commit.gpgSign)
help:
@echo "clean-build - remove build artifacts"
@echo "clean-pyc - remove Python file artifacts"
@echo "isortfix - fixes the imports order"
@echo "lint - check style with flake8"
@echo "test - run tests quickly with the default Python"
@echo "testall - run tests on every Python version with tox"
Expand All @@ -23,6 +24,9 @@ clean-pyc:
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +

isortfix:
isort --recursive --skip migrations docs

lint:
tox -e lint

Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))

import doctest
import os

DIR = os.path.dirname('__file__')
Expand Down Expand Up @@ -306,7 +307,6 @@ def django_configure():

# -- Doctest configuration ----------------------------------------

import doctest

doctest_default_flags = (0
| doctest.DONT_ACCEPT_TRUE_FOR_1
Expand Down
41 changes: 34 additions & 7 deletions rest_framework_simplejwt/authentication.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from django.contrib.auth import get_user_model
from django.utils.translation import gettext_lazy as _
from rest_framework import HTTP_HEADER_ENCODING, authentication
from rest_framework import HTTP_HEADER_ENCODING, authentication, exceptions
from rest_framework.authentication import CSRFCheck

from .exceptions import AuthenticationFailed, InvalidToken, TokenError
from .settings import api_settings
from .state import User

AUTH_HEADER_TYPES = api_settings.AUTH_HEADER_TYPES

Expand All @@ -16,25 +17,51 @@
)


def enforce_csrf(request):
"""
Enforce CSRF validation.
"""
check = CSRFCheck()
# populates request.META['CSRF_COOKIE'], which is used in process_view()
check.process_request(request)
reason = check.process_view(request, None, (), {})
if reason:
# CSRF failed, bail with explicit error message
raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)


class JWTAuthentication(authentication.BaseAuthentication):
"""
An authentication plugin that authenticates requests through a JSON web
token provided in a request header.
"""
www_authenticate_realm = 'api'

def __init__(self):
self.user_model = get_user_model()

def authenticate(self, request):
header = self.get_header(request)
if header is None:
return None
if not api_settings.AUTH_COOKIE:
return None
raw_token = request.COOKIES.get(api_settings.AUTH_COOKIE) or None
else:
raw_token = self.get_raw_token(header)

raw_token = self.get_raw_token(header)
if raw_token is None:
return None

validated_token = self.get_validated_token(raw_token)

return self.get_user(validated_token), validated_token
user = self.get_user(validated_token)
if not user or not user.is_active:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is now handled by the new auth rules (we definitely need to update our changelog and documentation)...

return None

if api_settings.AUTH_COOKIE:
enforce_csrf(request)

return user, validated_token

def authenticate_header(self, request):
return '{0} realm="{1}"'.format(
Expand Down Expand Up @@ -107,8 +134,8 @@ def get_user(self, validated_token):
raise InvalidToken(_('Token contained no recognizable user identification'))

try:
user = User.objects.get(**{api_settings.USER_ID_FIELD: user_id})
except User.DoesNotExist:
user = self.user_model.objects.get(**{api_settings.USER_ID_FIELD: user_id})
except self.user_model.DoesNotExist:
raise AuthenticationFailed(_('User not found'), code='user_not_found')

if not user.is_active:
Expand Down
5 changes: 2 additions & 3 deletions rest_framework_simplejwt/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ class instead of a `User` model instance. Instances of this class act as
# inactive user
is_active = True

_groups = EmptyManager(auth_models.Group)
_user_permissions = EmptyManager(auth_models.Permission)

def __init__(self, token):
self.token = token
self._groups = EmptyManager(auth_models.Group)
self._user_permissions = EmptyManager(auth_models.Permission)

def __str__(self):
return 'TokenUser {}'.format(self.id)
Expand Down
11 changes: 6 additions & 5 deletions rest_framework_simplejwt/serializers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import importlib

from django.contrib.auth import authenticate
from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.models import update_last_login
from django.utils.translation import gettext_lazy as _
from rest_framework import exceptions, serializers

from .settings import api_settings
from .state import User
from .tokens import RefreshToken, SlidingToken, UntypedToken

rule_package, user_eligible_for_login = api_settings.USER_AUTHENTICATION_RULE.rsplit('.', 1)
Expand All @@ -24,7 +23,7 @@ def __init__(self, *args, **kwargs):


class TokenObtainSerializer(serializers.Serializer):
username_field = User.USERNAME_FIELD
username_field = get_user_model().USERNAME_FIELD

default_error_messages = {
'no_active_account': _('No active account found with the given credentials')
Expand Down Expand Up @@ -70,9 +69,10 @@ def validate(self, attrs):
data = super().validate(attrs)

refresh = self.get_token(self.user)
access = refresh.access_token

data['access'] = str(access)
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)

if api_settings.UPDATE_LAST_LOGIN:
update_last_login(None, self.user)
Expand Down Expand Up @@ -104,7 +104,8 @@ class TokenRefreshSerializer(serializers.Serializer):
def validate(self, attrs):
refresh = RefreshToken(attrs['refresh'])

data = {'access': str(refresh.access_token)}
access = refresh.access_token
data = {'access': str(access)}

if api_settings.ROTATE_REFRESH_TOKENS:
if api_settings.BLACKLIST_AFTER_ROTATION:
Expand Down
15 changes: 15 additions & 0 deletions rest_framework_simplejwt/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,23 @@
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),

# Cookie name. Enables cookies if value is set.
'AUTH_COOKIE': None,
# A string like "example.com", or None for standard domain cookie.
'AUTH_COOKIE_DOMAIN': settings.CSRF_COOKIE_DOMAIN,
# Whether the auth cookies should be secure (https:// only).
'AUTH_COOKIE_SECURE': settings.CSRF_COOKIE_SECURE,
# The path of the auth cookie.
'AUTH_COOKIE_PATH': settings.CSRF_COOKIE_PATH,
}

# Whether to set the flag restricting cookie leaks on cross-site requests.
# This can be 'Lax', 'Strict', or None to disable the flag. 'None' is supported in version 3.1 only
# CSRF_COOKIE_SAMESITE was introduced in django 2.1 https://docs.djangoproject.com/en/3.1/releases/2.1/#csrf
if hasattr(settings, 'CSRF_COOKIE_SAMESITE'):
DEFAULTS['AUTH_COOKIE_SAMESITE'] = settings.CSRF_COOKIE_SAMESITE

IMPORT_STRINGS = (
'AUTH_TOKEN_CLASSES',
'TOKEN_USER_CLASS',
Expand Down
3 changes: 0 additions & 3 deletions rest_framework_simplejwt/state.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from django.contrib.auth import get_user_model

from .backends import TokenBackend
from .settings import api_settings

User = get_user_model()
token_backend = TokenBackend(api_settings.ALGORITHM, api_settings.SIGNING_KEY,
api_settings.VERIFYING_KEY, api_settings.AUDIENCE, api_settings.ISSUER)
Loading