Skip to content

Add JWT Authentication #1

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

Merged
merged 8 commits into from
Jul 5, 2020
Merged
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
15 changes: 14 additions & 1 deletion config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import os

from datetime import timedelta

from .env_reader import env

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
Expand All @@ -33,7 +35,8 @@
# Application definition

LOCAL_APPS = [
'styleguide_example.users.apps.UsersConfig'
'styleguide_example.api.apps.ApiConfig',
'styleguide_example.users.apps.UsersConfig',
]

THIRD_PARTY_APPS = [
Expand Down Expand Up @@ -142,3 +145,13 @@
# https://docs.djangoproject.com/en/3.0/howto/static-files/

STATIC_URL = '/static/'

# https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=30),
'REFRESH_TOKEN_LIFETIME': timedelta(days=31),
# TODO: Open issue for having a callable with a user here
'SIGNING_KEY': env('DJANGO_JWT_SIGNING_KEY', default=SECRET_KEY)
# TODO: https://github.com/SimpleJWT/django-rest-framework-simplejwt/pull/157/files
# Add settings for http support
}
3 changes: 2 additions & 1 deletion config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(('styleguide_example.api.urls', 'api'))),
]
1 change: 1 addition & 0 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ Django==3.0.7
django-environ==0.4.5
psycopg2==2.8.5
djangorestframework==3.11.0
djangorestframework-simplejwt==4.4.0
1 change: 1 addition & 0 deletions requirements/local.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
pytest==5.4.3
pytest-django==3.9.0
flake8==3.8.3
ipdb==0.13.3
Empty file.
5 changes: 5 additions & 0 deletions styleguide_example/api/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class ApiConfig(AppConfig):
name = 'styleguide_example.api'
Empty file.
7 changes: 7 additions & 0 deletions styleguide_example/api/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication


class ApiAuthMixin:
authentication_classes = (JWTAuthentication, )
permission_classes = (IsAuthenticated, )
7 changes: 7 additions & 0 deletions styleguide_example/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.urls import path, include

urlpatterns = [
path(
'auth/', include(('styleguide_example.authentication.urls', 'authentication'))
),
]
Empty file.
21 changes: 21 additions & 0 deletions styleguide_example/authentication/apis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from rest_framework.views import APIView
from rest_framework.response import Response

from rest_framework_simplejwt.views import (
TokenObtainPairView,
)

from styleguide_example.api.mixins import ApiAuthMixin

from styleguide_example.users.selectors import user_get_login_data


class UserLoginApi(TokenObtainPairView):
pass


class UserMeApi(ApiAuthMixin, APIView):
def get(self, request):
data = user_get_login_data(user=request.user)

return Response(data)
5 changes: 5 additions & 0 deletions styleguide_example/authentication/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class AuthenticationConfig(AppConfig):
name = 'styleguide_example.authentication'
Empty file.
Empty file.
Empty file.
34 changes: 34 additions & 0 deletions styleguide_example/authentication/tests/apis/test_user_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from django.test import TestCase
from django.urls import reverse

from rest_framework.test import APIClient

from styleguide_example.users.models import BaseUser


class UserLoginTests(TestCase):
def setUp(self):
self.client = APIClient()

def test_non_existing_user_cannot_login(self):
self.assertEqual(0, BaseUser.objects.count())

url = reverse('api:authentication:login')
data = {
'email': '[email protected]',
'password': 'hacksoft'
}

response = self.client.post(url, data)

# {'detail': ErrorDetail(string='No active account found with the given credentials', code='no_active_account')}

self.assertEqual(401, response.status_code)

def test_existing_user_can_login_and_access_apis(self):
"""
1. Create user
2. Assert login is OK
3. Call /api/auth/me
4. Assert valid response
"""
16 changes: 16 additions & 0 deletions styleguide_example/authentication/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.urls import path

from .apis import UserLoginApi, UserMeApi

urlpatterns = [
path(
'login/',
UserLoginApi.as_view(),
name='login'
),
path(
'me/',
UserMeApi.as_view(),
name='me'
),
]
11 changes: 11 additions & 0 deletions styleguide_example/users/selectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from styleguide_example.users.models import BaseUser


def user_get_login_data(*, user: BaseUser):
return {
'id': user.id,
'email': user.email,
'is_active': user.is_active,
'is_admin': user.is_admin,
'is_superuser': user.is_superuser,
}