Skip to main content

JWT authentication from email login codes.

Project description

JSON Web Token Email Authentiation

pip install jwt-email-auth

This module enables JSON Web Token Authentication in Django Rest framework without using Django's User model. Instead, login information is stored in cache, sent to the user's inbox, and obtained using a code that was sent to the given email.

Requirements:


Setup

  1. Add jwt_email_auth to Django's installed apps.
INSTALLED_APPS = [
    ...
    "jwt_email_auth",
    ...
]
  1. Add authentication, login, and token refresh views to urlpatterns.
from django.urls import path
from jwt_email_auth.views import SendLoginCodeView, LoginView, RefreshTokenView

urlpatterns = [
    ...
    path("authentication/", SendLoginCodeView.as_view(), name="authentication"),
    path("login/", LoginView.as_view(), name="login"),
    path("refresh/", RefreshTokenView.as_view(), name="refresh"),
    ...
]

# Or use routers...
# --------------------------------------------------------------------------------

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from jwt_email_auth.views import SendLoginCodeView, LoginView, RefreshTokenView

router = DefaultRouter()
router.register(r"authentication", SendLoginCodeView, "authentication")
router.register(r"login", LoginView, "login")
router.register(r"refresh", RefreshTokenView, "refresh")

urlpatterns = [
    ...
    path("", include(router.urls)),
    ...
]
  1. Configure JWT email auth settings.
JWT_EMAIL_AUTH = {
    # Required:
    # 
    # Off by default, will log message instead if False.
    "SEND_EMAILS": True,
    # A path to a function, in dot import notation, 
    # that returns what should be stored in cache before login 
    # codes are sent. This data is then added to the JWT claims 
    # when the login is successful. Note that 'code' is reserved 
    # for the login code that the login is made with, and it's not 
    # added to JWT claims during login.
    "LOGIN_DATA": "path.to.function",
   
    # Other useful ones:
    # 
    # Skip login code checks, for development.
    "SKIP_CODE_CHECKS": True,
    # How long tokens are valid. By default, access tokens 
    # are valid for 5 minutes, and refresh tokens for 14 days.
    "ACCESS_TOKEN_LIFETIME": timedelta(...),
    "REFRESH_TOKEN_LIFETIME": timedelta(...),
    # How long login codes are valid in cache (5 minutes by default).
    "LOGIN_CODE_LIFETIME": timedelta(...),
    # Path to an alternative code generator function, in dot import notation.
    "CODE_GENERATOR": "path.to.function",
    # The message to send. Should contain '{code}' and '{valid}' which will be 
    # replaced by the login code and valid time in minutes.
    "LOGIN_EMAIL_MESSAGE": "...",
    # Path to login HTML template. Context for this will include two values, 
    # 'code' (code needed for login) and 'valid' (code valid time in minutes).
    "LOGIN_EMAIL_HTML_TEMPLATE": "templates/example_login.html",
    # List of extra claims that token validation expects to find inside the token.
    # If not all of these are found, token is deemed invalid. Off by default.
    "EXPECTED_CLAIMS": [...],
    # How many times login can be attemted before used is banned 
    # for a short while (10 by default).
    "LOGIN_ATTEMPTS": 10,
    # How long user needs to wait from last login attempt until login 
    # ban is lifted (5 minutes by default)
    "LOGIN_COOLDOWN": timedelta(...),
}

Have a look at the provided settings for more.

  1. Configure Django's email email settings.
# Not all of these may be required
EMAIL_HOST_USER = ...
EMAIL_HOST_PASSWORD = ...
EMAIL_HOST = ...
EMAIL_PORT = ...
EMAIL_USE_TLS = ...
EMAIL_USE_SSL = ...
EMAIL_BACKEND = ...
EMAIL_SENDER = ...
EMAIL_SUBJECT_PREFIX = ...
DEFAULT_FROM_EMAIL = ...
SERVER_EMAIL = ...
  1. Add OpenSSH based ed25519 SIGNING_KEY (in PEM format) to environment variables. You can create one with, e.g., ssh-keygen using the command ssh-keygen -t ed25519. The linebreaks in PEM format should be replaced with | (pipe) characters. If you do not want to use environment variables, override the SIGNING_KEY setting.

A default signing key is provided for reference, but this should obviously be changed in production environments.

  1. (Optional) Add custom authentication classes to Rest framework settings.
REST_FRAMEWORK = {
    ...
    "DEFAULT_AUTHENTICATION_CLASSES": [
        ...
        "jwt_email_auth.authentication.JWTAuthentication",
        ...
    ],
    "DEFAULT_PERMISSION_CLASSES": [
       ...
       "jwt_email_auth.permissions.HasValidJWT",
       ...
    ]
    ...
}

Usage

  1. Send a login code to the authentication endpoint (from SendLoginCodeView class).
Request Response
POST [Authentication URI]
Content-Type: application/json
HTTP 204 NO CONTENT
{
"email":"person@example.com"
}
...
  1. POST the login code and email to login endpoint (from LoginView class).
Request Response
POST [Login URI]
Content-Type: application/json
HTTP 204 ACCEPTED
{
"email":"person@example.com"
"code":"123222"
}
{
"access":"..."
"refresh":"..."
}
  1. Refresh access token from the refresh token endpoint (from RefreshTokenView class).
Request Response
POST [Refresh Token URI]
Content-Type: application/json
HTTP 200 OK
{
"token":"..."
}
{
"access":"..."
}

Authentication and Permission classes

Add the JWTAuthentication or HasValidJWT to Rest framework's settings or or to the classe's authentication or permission classes

from rest_framework.views import APIView
from jwt_email_auth.authentication import JWTAuthentication
from jwt_email_auth.permissions import HasValidJWT


class SomeView(APIView):
   
   authentication_classes = [JWTAuthentication]
   permission_classes = [HasValidJWT]
   
   ...

Base Access Serializer

If you need to use claims from the token in you code, you can use the BaseAccessSerializer.

from rest_framework.views import APIView
from rest_framework.authentication import get_authorization_header
from jwt_email_auth.serializers import BaseAccessSerializer


class SomeSerializer(BaseAccessSerializer):
   
   take_form_token = ["example", "values"]
   ...


class SomeView(APIView):
   
    def post(self, request, *args, **kwargs):
   
        prefix, encoded_token = get_authorization_header(request).split()
        serializer = SomeSerializer(data={"token": encoded_token})
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data  # use .validated_data and not .data!
      
        print(data)  # {"example": ..., "values": ...}
        ...

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jwt-email-auth-0.0.4.tar.gz (14.8 kB view hashes)

Uploaded Source

Built Distribution

jwt_email_auth-0.0.4-py3-none-any.whl (16.0 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page