Skip to content

Proof of concept authentication module through JSON Web Tokens for G3W-SUITE.

License

Notifications You must be signed in to change notification settings

g3w-suite/g3w-admin-authjwt

Repository files navigation

G3W-ADMIN-AUTHJWT v0.0.2

A proof of concept authentication module through JSON Web Tokens for G3W-SUITE.

cookie-vs-token-auth

Installation

Install jwt module into g3w-admin applications folder:

# Install module from github (v1.0.0)
pip3 install git+https://github.com/g3w-suite/g3w-admin-authjwt.git@v1.0.0

# Install module from github (master branch)
# pip3 install git+https://github.com/g3w-suite/g3w-admin-authjwt.git@master

# Install module from local folder (git development)
# pip3 install /g3w-admin/g3w-admin/authjwt

# Install module from PyPi (not yet available)
# pip3 install g3w-admin-authjwt

Enable 'authjwt' module adding it to G3W_LOCAL_MORE_APPS list:

# local_settings.py

G3WADMIN_LOCAL_MORE_APPS = [
    ...
    'authjwt'
    ...
]

Refer to g3w-suite-docker repository for more info about running this on a docker instance.

NB On Ubuntu Jammy you could get an UNKNOWN package install instead of g3w-admin-authjwt, you can retry installing it as follows to fix it:

# Fix: https://github.com/pypa/setuptools/issues/3269#issuecomment-1254507377
export DEB_PYTHON_INSTALL_LAYOUT=deb_system

# And then install again the module
pip3 install ...

Configuration

The following packages are included in this module:

Refer to the official docs for a more comprehensive list of the available settings:

# Customize Django CORS Headers (v3.11.0)
# -------------------------------------------

CORS_ALLOW_ALL_ORIGINS = True # NB: DEVELOPMENT ONLY!

CORS_ALLOWED_ORIGINS = [      # NB: DIFFERENT PORT == DIFFERENT SERVER
     'http://localhost:8080',
     'http://localhost:8081',
     'http://127.0.0.1:8080',
     'http://127.0.0.1:8081',
]
# Customize Django REST Framework - Simple JWT (v5.2.2)
# -------------------------------------------

from datetime import timedelta

JWT_AUTH = {
    'JWT_ALLOW_REFRESH': True,
    'JWT_EXPIRATION_DELTA': timedelta(hours=1),        
    'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=7), # 
}

# NB: longer-lived tokens can reduce HTTP traffic
# but they also highlight "health check" issues after
# user logout (given the stateless nature of JWT requests,
# if not handled for example through a "token blacklist",
# the user could still appear as logged-in even after a logout)
# See also:
# - https://django-rest-framework-simplejwt.readthedocs.io/en/latest/blacklist_app.html
# - https://django-rest-framework-simplejwt.readthedocs.io/en/latest/stateless_user_authentication.html

For the default settings currently applied by this module, see also: authjwt/__init__.py

API URLs

Check the authjwt/apiurls.py file for a comprehensive list and how to use them.

Find out that they are loaded correctly in your project by running the following command in a terminal shell:

python3 manage.py show_urls
/authjwt/api/                   rest_framework.routers.APIRootView                      api-root
/authjwt/api/\.<format>/        rest_framework.routers.APIRootView                      api-root
/authjwt/api/ping/              authjwt.views.PingViewSet                               ping-list
/authjwt/api/ping\.<format>/    authjwt.views.PingViewSet                               ping-list
/authjwt/api/token/             rest_framework_simplejwt.views.TokenObtainPairView      token_obtain_pair
/authjwt/api/token/blacklist/   rest_framework_simplejwt.views.TokenBlacklistView       token_blacklist
/authjwt/api/token/refresh/     rest_framework_simplejwt.views.TokenRefreshView         token_refresh
/authjwt/api/token/verify/      rest_framework_simplejwt.views.TokenVerifyView          token_verify

Tip: if you are developing locally use a software like httpie (or postman) to import and save the following curl commands for later use:

Login user

curl --request POST \
  --url http://localhost:8000/authjwt/api/token/ \
  --header 'Content-Type: application/json' \
  --data '{ "username":"admin", "password":"admin" }'

Refresh token

curl --request POST \
  --url http://localhost:8000/authjwt/api/token/refresh/ \
  --header 'Content-Type: application/json' \
  --data '{ "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY3MTI2NTIyNiwiaWF0IjoxNjcxMTc4ODI2LCJqdGkiOiI3OGFiODU2MjcyZWM0YjAxOWI1Y2M4NTA1ZmNiMTIwOSIsInVzZXJfaWQiOjJ9.AAKmj8I3IN936PrOcxqGmsImWVkFk2AtsFJSE_o4dlY" }'

Verify token

curl --request POST \
  --url http://localhost:8000/authjwt/api/token/verify/ \
  --header 'Content-Type: application/json' \
  --data '{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY3MTI3NTg4NSwiaWF0IjoxNjcxMTg5NDg1LCJqdGkiOiIxMTk2NWNiNGFkYjE0ZmEzOTUxYzBhOTkxNDlhZWMwNyIsInVzZXJfaWQiOjJ9.YA4MesWfQcbYip6EhRxZoQAFxoZeBdlJdCrEme8sTc0" }'

Heartbeat (check access to a protected view)

curl --request GET \
  --url 'http://localhost:8000/authjwt/api/ping/?id=pong' \
  --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjcxMTgyNDIxLCJpYXQiOjE2NzExODIxMjEsImp0aSI6IjI5YjQyN2ZlYjRkMjQ3YmM4NDAzODcyY2VhOTM2NWI5IiwidXNlcl9pZCI6Mn0.P6E7r9BCEFMzkTohJR4EMW1m8wm4DGZ03232mJO6vQI'

Logout user (optional)

curl --request POST \
  --url http://localhost:8000/authjwt/api/token/blacklist/ \
  --header 'Content-Type: application/json' \
  --data '{ "refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY3MTI3NTg4NSwiaWF0IjoxNjcxMTg5NDg1LCJqdGkiOiIxMTk2NWNiNGFkYjE0ZmEzOTUxYzBhOTkxNDlhZWMwNyIsInVzZXJfaWQiOjJ9.YA4MesWfQcbYip6EhRxZoQAFxoZeBdlJdCrEme8sTc0" }'

Contributing

Steps to follow for local development of this module.

Traditional workflow

Steps to follow in case of a regular install.

Clone and place the g3w-admin-authjwt repository into g3w-admin applications folder:

cd /path/to/your/development/workspace

git clone https://github.com/g3w-suite/g3w-admin.git ./g3w-admin
git clone https://github.com/g3w-suite/g3w-admin-authjwt.git ./g3w-admin/g3w-admin/authjwt

So your folder structure should matches the following:

.
└── g3w-admin/
    ├── g3w-admin/
    │   ├── authjwt/
    │   │   ├── authjwt/
    │   │   │   ├── __init__.py
    │   │   │   ├── apps.py
    │   │   │   ├── urls.py
    │   │   │   ├── views.py
    │   │   │   └── ...
    │   │   ├── pyproject.toml
    │   │   └── README.md
    │   ├── base/
    │   ├── core/
    │   ├── ...
    │   └── manage.py
    └── README.md

Install the g3w-admin-authjwt module from the local source folder:

pip3 install /g3w-admin/g3w-admin/authjwt

Then activate the 'authjwt' module as usual by adding it to G3W_LOCAL_MORE_APPS list.

Alternative workflow

Steps to follow in case of a editable install.

Clone g3w-admin and g3w-admin-authjwt repositories into two adjacent folders:

cd /path/to/your/development/workspace

git clone https://github.com/g3w-suite/g3w-admin.git
git clone https://github.com/g3w-suite/g3w-admin-authjwt.git

So your folder structure should matches the following:

.
├── g3w-admin/
│   ├── g3w-admin/
│   │   ├── base/
│   │   ├── core/
│   │   ├── ...
│   │   └── manage.py
│   └── README.md
│
└── g3w-admin-authjwt/
    ├── authjwt/
    │   ├── __init__.py
    │   ├── apps.py
    │   ├── urls.py
    │   ├── views.py
    │   └── ...
    ├── pyproject.toml
    └── README.md

Install the g3w-admin-authjwt module in editable mode starting from your g3w-admin folder:

cd g3w-admin

python3 -m pip install -e ../g3w-admin-authjwt

Then activate the 'authjwt' module as usual by adding it to G3W_LOCAL_MORE_APPS list.

Publish

Create a new git tag that is appropriate for the version you intend to publish, eg:

git tag -a v1.0.1
git push origin v1.0.1
Publishing on the Python Package Index

Steps to follow when releasing a new software version on PyPi.

First make sure you have the latest versions of pip, build and twine installed:

python3 -m pip install --upgrade pip
python3 -m pip install --upgrade build
python3 -m pip install --upgrade twine

Build the dist folder starting from the same directory where pyproject.toml is located:

python3 -m build

Upload all to PyPI and verify things look right:

twine upload dist/*

TODO

Find out if it could be feasible to code a sort of proxy class for the rest_framework.permissions.IsAuthenticated (or for the rest_framework.viewsets) in order to make use of JWT Authentication with legacy API endpoints that make use of the django.contrib.auth.decorators.login_required method to check if a user is authenticated, ie:

# apiurls.py

from django.contrib.auth.decorators import login_required

...

urlpatterns = [

  path(
    'api/some-protected-view/',
    login_required(SomeProtectedView.as_view()), # currently "SomeProtectedView" doesn't support JWT Auth
    name='some-protected-view'
  )

]

Related resources

Code samples on how to implement JWT with Vue and Django Rest Framework:

Sample project on how to develop a complete docker stack (backend + frontend):

Sample project on how to implement a "simplejwt" extension without using the Django Rest Framework:

Packaging a Python project:


Compatibile with: g3w-admin version g3w-suite-docker version


License: MPL-2