Skip to content

Commit d7e2c61

Browse files
authored
Use SimpleJWT to generate Tokens (#92)
1 parent 5174beb commit d7e2c61

File tree

18 files changed

+400
-200
lines changed

18 files changed

+400
-200
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ on:
88
branches: [ master ]
99
pull_request:
1010
branches: [ master ]
11-
# Allow rebuilds via API.
12-
repository_dispatch:
13-
types: rebuild
1411

1512
jobs:
1613
tests:

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@
1212
---
1313
**User APP for Django REST Framework with API Views.**<br>
1414

15-
`DRF User` is a Django app that overrides default user app to provide additional
16-
attributes and functionalities. The current stable version includes:
15+
`DRF User` is a Django app that overrides default user app to provide additional attributes and functionalities. The
16+
current stable version includes:
1717

18+
- [x] JWT Support (Using [Simple JWT](https://django-rest-framework-simplejwt.readthedocs.io/))
1819
- [x] Mobile Number
1920
- [x] Single field for full name
2021
- [x] REST API to register
2122
- [x] REST API to login
22-
- [x] MultiModelBackend: User can login using either of mobile, email or
23-
username
23+
- [x] MultiModelBackend: User can login using either of mobile, email or username
2424
- [x] REST API to login with OTP (Same API endpoint as for OTP Verification; Set
25-
`is_login: true` while sending JSON request)
25+
`is_login: true` while sending JSON request)
2626
- [x] OTP Verification for mobile and email
2727
- [x] API to register / login with OTP (no pre-registration required)
2828
- [x] API to set user's profile image
@@ -38,8 +38,8 @@ attributes and functionalities. The current stable version includes:
3838
# Documentation
3939

4040
---
41-
- For more information on installation and configuration see the documentation
42-
at: https://drf-user.readthedocs.io/
41+
42+
- For more information on installation and configuration see the documentation at: https://drf-user.readthedocs.io/
4343

4444
# Example
4545

@@ -70,6 +70,7 @@ Take a look at `http://localhost:8000/swagger`. Swagger will list all the APIs o
7070
# Contributing
7171

7272
---
73+
7374
- Please file bugs and send pull requests to the
7475
[GitHub Repository](https://github.com/101loop/drf-user) and
7576
[Issue Tracker](https://github.com/101loop/drf-user/issues). See

docs/api-doc.rst

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ API Docs for Login.
5050
:jsonparam str password: password of user
5151

5252
:statuscode 200: if supplied params are valid
53-
:statuscode 422: if supplied params are invalid
53+
:statuscode 400: if some fields are missing
54+
:statuscode 401: if supplied params are invalid
5455

5556
Account
5657
-------
@@ -74,7 +75,7 @@ API Docs for Account.
7475
}
7576
7677
:statuscode 200: if request is authenticated
77-
:statuscode 403: if request is not authenticated
78+
:statuscode 401: if request is not authenticated
7879

7980
|
8081
@@ -100,7 +101,7 @@ API Docs for Account.
100101

101102
:statuscode 200: if request is authenticated
102103
:statuscode 400: if any param is not supplied
103-
:statuscode 403: if request is not authenticated
104+
:statuscode 401: if request is not authenticated
104105

105106
|
106107
@@ -123,7 +124,7 @@ API Docs for Account.
123124

124125
:statuscode 200: if request is authenticated
125126
:statuscode 400: if any param is not supplied
126-
:statuscode 403: if request is not authenticated
127+
:statuscode 401: if request is not authenticated
127128

128129
OTP
129130
---
@@ -238,7 +239,7 @@ Upload Image
238239

239240
API Docs for Upload Image.
240241

241-
.. http:post:: /uploadimage/
242+
.. http:post:: /upload-image/
242243
243244
Upload user's profile image.
244245

@@ -252,4 +253,26 @@ API Docs for Upload Image.
252253

253254
:statuscode 201: if supplied params are valid
254255
:statuscode 400: if image not passed
255-
:statuscode 403: if jwt token is not passed or invalid
256+
:statuscode 401: if supplied token is invalid
257+
258+
Refresh Token
259+
------------
260+
261+
API Docs for Refresh Token.
262+
263+
.. http:post:: /refresh-token/
264+
265+
When short-lived access token expires, you can use the longer-lived refresh
266+
token to obtain another access token.
267+
268+
.. code-block:: json
269+
270+
{
271+
"refresh": "generated refresh token"
272+
}
273+
274+
:jsonparam str refresh: refresh token
275+
276+
:statuscode 200: if supplied refresh token is valid
277+
:statuscode 400: if refresh token is not passed
278+
:statuscode 401: if refresh token is invalid

docs/index.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@
77
Welcome to drf-user's documentation!
88
====================================
99

10+
.. image:: https://readthedocs.org/projects/drf-instamojo/badge/?version=latest
11+
:target: https://drf-bulk.readthedocs.io/en/latest/
12+
:alt: Documentation Status
13+
.. image:: https://github.com/101loop/drf-user/workflows/CI/badge.svg
14+
:target: https://github.com/101loop/drf-user
15+
:alt: CI
16+
.. image:: https://codecov.io/gh/101Loop/drf-user/branch/master/graph/badge.svg?token=e0AVdjOABf
17+
:target: https://codecov.io/gh/101Loop/drf-user
18+
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
19+
:target: https://github.com/psf/black
20+
:alt: Code style: black
21+
1022
User APP for Django REST Framework with API Views.
1123

1224

@@ -30,6 +42,8 @@ Overview
3042
Feature List
3143
============
3244

45+
|check_| JWT Support (Using `Simple JWT <https://django-rest-framework-simplejwt.readthedocs.io/>`__)
46+
3347
|check_| Mobile Number
3448

3549
|check_| Single field for full name

docs/installation.rst

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Or Install from ``source code``::
2222
Requirements
2323
------------
2424

25-
``drf-user`` supports Python 3.5 and above.
25+
``drf-user`` supports Python 3.6 and above.
2626

2727
Prerequisites
2828
-------------
@@ -34,7 +34,6 @@ Prerequisites
3434
INSTALLED_APPS = [
3535
...
3636
'drf_user',
37-
'drfaddons',
3837
'rest_framework',
3938
'django_filters',
4039
...
@@ -77,53 +76,56 @@ Or if you have `regex` based urls use `re_path`
7776
.. code-block:: python
7877
7978
AUTHENTICATION_BACKENDS = [
80-
'drf_user.auth.MultiFieldModelBackend',
79+
'drf_user.auth.MultiFieldModelBackend', # to support login with email/mobile
8180
]
8281
83-
84-
* Set JWT_AUTH configurations in ``settings.py`` (`these are default values to run drf_user, update as per your requirements`)
85-
86-
.. code-block:: python
87-
88-
import datetime
89-
90-
91-
JWT_AUTH = {
92-
"JWT_ENCODE_HANDLER": "rest_framework_jwt.utils.jwt_encode_handler",
93-
"JWT_DECODE_HANDLER": "rest_framework_jwt.utils.jwt_decode_handler",
94-
"JWT_PAYLOAD_HANDLER": "drf_user.auth.jwt_payload_handler",
95-
"JWT_PAYLOAD_GET_USER_ID_HANDLER": "rest_framework_jwt.utils.jwt_get_user_id_from_payload_handler",
96-
"JWT_RESPONSE_PAYLOAD_HANDLER": "rest_framework_jwt.utils.jwt_response_payload_handler",
97-
"JWT_SECRET_KEY": SECRET_KEY,
98-
"JWT_GET_USER_SECRET_KEY": None,
99-
"JWT_PUBLIC_KEY": None,
100-
"JWT_PRIVATE_KEY": None,
101-
"JWT_ALGORITHM": "HS256",
102-
"JWT_VERIFY": True,
103-
"JWT_VERIFY_EXPIRATION": True,
104-
"JWT_LEEWAY": 0,
105-
"JWT_EXPIRATION_DELTA": datetime.timedelta(weeks=99999),
106-
"JWT_AUDIENCE": None,
107-
"JWT_ISSUER": None,
108-
"JWT_ALLOW_REFRESH": False,
109-
"JWT_REFRESH_EXPIRATION_DELTA": datetime.timedelta(days=7),
110-
"JWT_AUTH_HEADER_PREFIX": "Bearer",
111-
"JWT_AUTH_COOKIE": None,
112-
}
113-
11482
* Set `DEFAULT_AUTHENTICATION_CLASSES` in `REST_FRAMEWORK` configuration in your ``settings.py``
11583

11684
.. code-block:: python
11785
11886
REST_FRAMEWORK = {
11987
...
12088
'DEFAULT_AUTHENTICATION_CLASSES': (
121-
'drfaddons.auth.JSONWebTokenAuthenticationQS',
89+
'rest_framework_simplejwt.authentication.JWTAuthentication',
12290
...
12391
),
12492
...
12593
}
12694
95+
96+
* Set `SIMPLE_JWT` configurations in ``settings.py`` (`these are default values from Simple JWT, update as per your requirements`)
97+
98+
.. code-block:: python
99+
100+
from datetime import timedelta
101+
102+
...
103+
104+
# see https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html
105+
SIMPLE_JWT = {
106+
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
107+
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
108+
"ROTATE_REFRESH_TOKENS": False,
109+
"BLACKLIST_AFTER_ROTATION": True,
110+
"UPDATE_LAST_LOGIN": True,
111+
"ALGORITHM": "HS256",
112+
"SIGNING_KEY": SECRET_KEY,
113+
"VERIFYING_KEY": None,
114+
"AUDIENCE": None,
115+
"ISSUER": None,
116+
"AUTH_HEADER_TYPES": ("Bearer",),
117+
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
118+
"USER_ID_FIELD": "id",
119+
"USER_ID_CLAIM": "user_id",
120+
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
121+
"TOKEN_TYPE_CLAIM": "token_type",
122+
"JTI_CLAIM": "jti",
123+
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
124+
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
125+
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
126+
}
127+
128+
127129
* Finally, run ``migrate`` command
128130

129131
.. code-block:: shell

drf_user/auth.py

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,9 @@
44
Author: Himanshu Shankar (https://himanshus.com)
55
"""
66
import re
7-
import uuid
8-
from calendar import timegm
9-
from datetime import datetime
107

118
from django.contrib.auth import get_user_model
129
from django.contrib.auth.backends import ModelBackend
13-
from rest_framework_jwt.compat import get_username
14-
from rest_framework_jwt.compat import get_username_field
15-
from rest_framework_jwt.settings import api_settings
1610

1711

1812
class MultiFieldModelBackend(ModelBackend):
@@ -82,55 +76,3 @@ def get_user(self, username: int) -> None:
8276
return self.user_model.objects.get(pk=username)
8377
except self.user_model.DoesNotExist:
8478
return None
85-
86-
87-
def jwt_payload_handler(user):
88-
"""
89-
A custom JWT Payload Handler that adds certain extra data in
90-
payload such as: email, mobile, name
91-
92-
Source: Himanshu Shankar (https://github.com/iamhssingh)
93-
Parameters
94-
----------
95-
user: get_user_model()
96-
97-
Returns
98-
-------
99-
payload: dict
100-
"""
101-
username_field = get_username_field()
102-
username = get_username(user)
103-
104-
payload = {
105-
"user_id": user.pk,
106-
"is_admin": user.is_staff,
107-
"exp": datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA,
108-
}
109-
110-
if hasattr(user, "email"):
111-
payload["email"] = user.email
112-
113-
if hasattr(user, "mobile"):
114-
payload["mobile"] = user.mobile
115-
116-
if hasattr(user, "name"):
117-
payload["name"] = user.name
118-
119-
if isinstance(user.pk, uuid.UUID):
120-
payload["user_id"] = str(user.pk)
121-
122-
payload[username_field] = username
123-
124-
# Include original issued at time for a brand new token,
125-
# to allow token refresh
126-
127-
if api_settings.JWT_ALLOW_REFRESH:
128-
payload["orig_iat"] = timegm(datetime.utcnow().utctimetuple())
129-
130-
if api_settings.JWT_AUDIENCE is not None:
131-
payload["aud"] = api_settings.JWT_AUDIENCE
132-
133-
if api_settings.JWT_ISSUER is not None:
134-
payload["iss"] = api_settings.JWT_ISSUER
135-
136-
return payload
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Generated by Django 3.1.2 on 2021-01-18 13:04
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('drf_user', '0007_auto_20201031_2020'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='authtransaction',
15+
name='expires_at',
16+
field=models.DateTimeField(blank=True, null=True, verbose_name='Expires At'),
17+
),
18+
migrations.AddField(
19+
model_name='authtransaction',
20+
name='refresh_token',
21+
field=models.TextField(blank=True, verbose_name='JWT Refresh Token'),
22+
),
23+
migrations.AlterField(
24+
model_name='authtransaction',
25+
name='token',
26+
field=models.TextField(verbose_name='JWT Access Token'),
27+
),
28+
]

drf_user/models.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,15 @@ class AuthTransaction(models.Model):
9898
"""
9999

100100
ip_address = models.GenericIPAddressField(blank=False, null=False)
101-
token = models.TextField(verbose_name=_("JWT Token passed"))
101+
token = models.TextField(verbose_name=_("JWT Access Token"))
102102
session = models.TextField(verbose_name=_("Session Passed"))
103+
refresh_token = models.TextField(
104+
blank=True,
105+
verbose_name=_("JWT Refresh Token"),
106+
)
107+
expires_at = models.DateTimeField(
108+
blank=True, null=True, verbose_name=_("Expires At")
109+
)
103110
create_date = models.DateTimeField(
104111
verbose_name=_("Create Date/Time"), auto_now_add=True
105112
)

0 commit comments

Comments
 (0)