Usage
Backend
- 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" } |
... |
- POST the login code and email to login endpoint (from
LoginView
class).
Request | Response |
---|---|
POST [Login URI] Content-Type: application/json |
HTTP 200 OK |
{ "email":"person@example.com" "code":"123222" } |
{ "access":"..." "refresh":"..." } |
If both
USE_TOKENS
andUSE_COOKIES
are enabled, you can select the wanted login method via thePrefer
header´. Set it totoken
orcookies
to select one or the other. If not set, the value will be determined fromDEFAULT_LOGIN_METHOD
, but if this is not set the default will becookies
if cookies are enabled, andtoken
if not.
- 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":"..." "refresh":"..." } |
If
ROTATE_REFRESH_TOKENS
is in use, the given refresh token will be a new refresh token, and the old refresh token will no longer be valid. Otherwise, the token will be the same same token used on the endpoint.
Authentication and Permission classes
Add the JWTAuthentication
or HasValidJWT
to Rest framework's settings,
or to the class's authentication_classes
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 import serializers
from rest_framework.views import APIView
from jwt_email_auth.serializers import BaseAccessSerializer
from jwt_email_auth.authentication import JWTAuthentication
from jwt_email_auth.permissions import HasValidJWT
class SomeSerializer(BaseAccessSerializer):
take_from_token = ["example", "values"]
some = serializers.CharField()
data = serializers.CharField()
...
class SomeView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [HasValidJWT]
def post(self, request, *args, **kwargs):
data = {"some": ..., "data": ...}
# Request is needed in serializer context to get the access token
serializer = SomeSerializer(data=data, context={"request", request})
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
# ...or this
# data = serializer.data
print(data) # {"some": ..., "data": ..., "example": ..., "values": ...}
...
Frontend
To implement authentication with this library in the frontend,
you'll need a way to call the SendLoginCodeView
and LoginView
for authentication, and then implement a way to automatically
call RefreshTokenView
when your access token expires or is about
to expire. You'll also need to implement views/logic for all the
possible error responses from each of these views.
SendLoginCodeView
- 400: Input is incorrect
- 412: User is blocked from login due to too many attempts
- 503: Could not send login code
LoginView
- 400: Input is incorrect
- 403: Given login code was incorrect
- 404: No login code has been sent for this user, or the code has expired
- 410: Login data was corrupted
- 412: User has been blocked due to too many attempts
RefreshTokenView
- 400: Input is incorrect
- 403: Refresh token expired, or otherwise invalid
- 404: Refresh token user no longer exists (requires "user_check=True" in refresh view)
When using the JWT in views using the JWTAuthentication
and HasValidJWT
authentication and permission classes, you need to always listen for 403
responses, and try to call RefreshTokenView
in case the access token has
expired. In case RefreshTokenView
also returns 403, this usually means
that the refresh token has also expired, and the user should be asked
to re-authorize.
In case you are using JWT rotation, when you call
RefreshTokenView
, the returned refresh token will also need to be saved,
as the old one is invalidated. Using JWT rotation can save your users from
having to reauthorize when their initial refresh token expires.
IP blocked?} check_email[Check given email
from third party] fail__user_blocked[Error: User cannot
send another
login code yet] email_found{User with
email found} send_login_code[Send login code
to given email] fail__email_not_found[Error: Did not
find user] start__enter_login_code['Enter login code'] fail__block_user[Error: Block user
from loggin in
for 5 minutes
based on IP] fail__re_enter_login_code[Error: Wrong
login code] resend_code[Resend code,
invalidate previous one] can_resend{Can resend
code?} fail__cannot_resend[Error: User cannot
send another
login code yet] login_code_correct{Valid code?} success__log_in(Log user in) too_many_attemps{Too many
attempts?} start__enter_email --> |User enters
their email| user_blocked user_blocked --> |no| check_email check_email --> email_found user_blocked --> |yes| fail__user_blocked fail__user_blocked --> start__enter_email email_found --> |yes| send_login_code email_found --> |no| fail__email_not_found fail__email_not_found --> start__enter_email send_login_code --> start__enter_login_code start__enter_login_code --> |Try another
email| start__enter_email start__enter_login_code --> |User enters
login code| login_code_correct start__enter_login_code --> |Resend code| can_resend can_resend --> |no| fail__cannot_resend can_resend --> |yes| resend_code resend_code --> start__enter_login_code fail__cannot_resend --> start__enter_login_code login_code_correct --> |yes| success__log_in login_code_correct --> |no| too_many_attemps too_many_attemps --> |no| fail__re_enter_login_code too_many_attemps --> |yes| fail__block_user fail__re_enter_login_code --> start__enter_login_code fail__block_user --> start__enter_email
A flowchart describing a possible implementation.
Non-email authentication
Even though "email" is in the name of the library, it can be used to authenticate via other means, e.g., SMS. The library tries not to refer to email specifically, so that all documentation is still relevant if the authentication method is something other than email.
All you'll need to do is create new serializers for SendLoginCodeView
and LoginView
which replace the "email" field with something else.
Then, you can implement SEND_LOGIN_CODE_CALLBACK
using that field's
value. Here is an example using phone number for SMS authentication:
# myapp/views.py
from rest_framework import serializers
from jwt_email_auth.serializers import BaseSendLoginCodeSerializer, BaseLoginSerializer
from jwt_email_auth import views as jwt_views
# Should have one field of any type
class SendLoginCodeSerializer(BaseSendLoginCodeSerializer):
phone = serializers.CharField(help_text="Phone number to send the code to.")
# Should have one field of any type
class LoginSerializer(BaseLoginSerializer):
phone = serializers.CharField(help_text="Phone number the code was sent to.")
class SendLoginCodeView(jwt_views.SendLoginCodeView):
serializer_class = SendLoginCodeSerializer
class LoginView(jwt_views.LoginView):
serializer_class = LoginSerializer
# myapp/utils.py
from typing import Any
from rest_framework.request import Request
def send_login_code_via_sms(
phone: str,
login_data: dict[str, Any],
request: Request,
) -> None:
...
# myproject/settings.py
JWT_EMAIL_AUTH = {
...
"SEND_LOGIN_CODE_CALLBACK": "myapp.utils.send_login_code_via_sms",
...
}