How to add authentication for users for django website using allauth and React

Clock
31.01.2025
An eye
33
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
1

Introduction

Yes, I haven't written about this site for a long time. And now I consider it my duty to finish it at any cost. So, we need to develop and add a system of authentication and registration of users to the site. How to do this, and what will it look like?
There is actually nothing complicated here. Think about it, we need to develop the following elements:
  1. Login form
  2. Registration form
  3. Password change form
  4. Logout form
  5. Email confirmation form + template
  6. Password reset form + template
  7. Modal window for the user profile
And, actually, that's all... Although AllAuth does not necessarily need to be integrated into the site, you can implement registration and user management by yourself. It will still be easier. We will only have to implement the frontend part of the site.
I do not really want to overcomplicate and overload this article with the implementation of JWT tokens or the implementation of such user account management architectures as SAML2 or LDAP, or even registration through social networks. This will be in the following projects and articles because it has already turned out to be too big.
That is, in this article I will describe the process of integrating the AllAuth library on a django site with the minimum necessary functionality.

Writing a frontend for the login and registration forms

First, let's prepare the containers in which we will draw our modal windows. Add the following lines of code to the base.html template, right before the "main" tag and after the end of the block with the site header:
...
{% block header %}
{% endblock %}
<div data-type="profile"><a href="#">profile</a></div>
...
<div id="auth-modal"></div>
<main class="flex-auto pl-2 pr-2 pt-3 pb-3">
{% block main %}
{% endblock %}
</main>
In my case, the tag with the data-type="profile" attribute will act as another button in the site header. It will open the user card. The user card will be rendered as a modal window, which will be located in the tag with id="auth-modal".
Now let's connect the new Auth component. This React component will draw modal windows for Login, Registration, the user card, and others. In the component file src/index.js, import:
import Header from './components/Header';
import Footer from './components/Footer';
import AppSettings from './components/AppSettings';
import AppUtils from './components/AppUtils';
import AppActions from './components/AppActions';
import AppQueries from './components/AppQueries';
import Msg from './components/Msg';
import Hint from './components/Tutorial';
import Auth from './components/Auth';
The component file itself implements a component called Auth, which determines via props which modal window to show and how to contact the server, respectively. Surely, there is a simpler and more elegant way to do everything that I have just done, but I just need points of communication with the server from which I can get templates for login and registration, and it is changing the site accordingly. Here is the template file src/components/Auth.js:
import * as React from 'react';
import { createRoot } from 'react-dom/client';
import Modal from '@mui/material/Modal';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button'
import axios from "axios";
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import Input from '@mui/material/Input';
import InputLabel from '@mui/material/InputLabel';
import IconButton from '@mui/material/IconButton';
import Link from '@mui/material/Link';
import Checkbox from '@mui/material/Checkbox';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import { Wait, StopWait, Msg } from './Waiter';

function getCookie (name) {
let cookieValue = null
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';')
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim()
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1))
break
}
}
}
return cookieValue
}
export function getCSRFToken () {
return getCookie('csrftoken')
}
const BASE_URL = `/_allauth/browser/v1`
export const URLs = Object.freeze({
// Meta
CONFIG: BASE_URL + '/config',
// Account management
CHANGE_PASSWORD: BASE_URL + '/account/password/change',
EMAIL: BASE_URL + '/account/email',
PROVIDERS: BASE_URL + '/account/providers',
// Account management: 2FA
AUTHENTICATORS: BASE_URL + '/account/authenticators',
RECOVERY_CODES: BASE_URL + '/account/authenticators/recovery-codes',
TOTP_AUTHENTICATOR: BASE_URL + '/account/authenticators/totp',
// Auth: Basics
LOGIN: BASE_URL + '/auth/login',
LOGOUT: BASE_URL + '/auth/session',
REQUEST_LOGIN_CODE: BASE_URL + '/auth/code/request',
CONFIRM_LOGIN_CODE: BASE_URL + '/auth/code/confirm',
SESSION: BASE_URL + '/auth/session',
REAUTHENTICATE: BASE_URL + '/auth/reauthenticate',
REQUEST_PASSWORD_RESET: BASE_URL + '/auth/password/request',
RESET_PASSWORD: BASE_URL + '/auth/password/reset',
SIGNUP: BASE_URL + '/auth/signup',
VERIFY_EMAIL: BASE_URL + '/auth/email/verify',
// Auth: 2FA
MFA_AUTHENTICATE: BASE_URL + '/auth/2fa/authenticate',
MFA_REAUTHENTICATE: BASE_URL + '/auth/2fa/reauthenticate',
// Auth: Social
PROVIDER_SIGNUP: BASE_URL + '/auth/provider/signup',
REDIRECT_TO_PROVIDER: BASE_URL + '/auth/provider/redirect',
PROVIDER_TOKEN: BASE_URL + '/auth/provider/token',
// Auth: Sessions
SESSIONS: BASE_URL + '/auth/sessions',
// Auth: WebAuthn
REAUTHENTICATE_WEBAUTHN: BASE_URL + '/auth/webauthn/reauthenticate',
AUTHENTICATE_WEBAUTHN: BASE_URL + '/auth/webauthn/authenticate',
LOGIN_WEBAUTHN: BASE_URL + '/auth/webauthn/login',
SIGNUP_WEBAUTHN: BASE_URL + '/auth/webauthn/signup',
WEBAUTHN_AUTHENTICATOR: BASE_URL + '/account/authenticators/webauthn'
})

var session = {
username: null,
email: {
address: null,
verified: null,
},
is_authenticated: false
}

const updateEmail = async () =>{
const {data} = await axios.get(URLs.EMAIL, {
headers: {
"accept": "application/json",
'Content-Type': "application/json",
}
}).catch(error=>{
return error.response
})
if (data.status === 200){
session.email.address = data.data[0].email
if (data.data[0].verified){
session.email.verified = 'verified'
}else{
session.email.verified = 'not verified'
}
}else if(data.status === 401){
session.email.address = null
session.email.verified = null
}
}

const updateSession = async () => {
const {data} = await axios.get(URLs.SESSION, {
headers: {
"accept": "application/json",
'Content-Type': "application/json",
}
}).catch(error=>{
return error.response
})

if (data.status === 200){
session.username = data.data.user.username
session.is_authenticated = true
}else if(data.status === 401){
session.username = null
session.is_authenticated = false
}
updateEmail()
}

function onRequestConfirmEmail(req, email){
}

function onRequestPasswordReset(req){
}

function fetchPasswordReset(req){
}

function onPasswordChange(req){
}

function fetchPasswordChange(req){
}

function fetchLogin(req){
}

function onLogin(req){
}

function fetchSignup(req){
}

function onSignup(req){
}

function onSignout(req){
}

function fetchProfile(req){
}

export default function Auth(props){

const openModal = (event) =>{
setModal(true);
}

const [isModal, setModal] = React.useState(false);
const modalRef = React.useRef(null)
const req = {'setModal': setModal, 'modal': modalRef}
const onInitBtn = document.getElementById(props.btnId)
onInitBtn.addEventListener(props.btnId, openModal)
return (
<Modal
ref={modalRef}
open={isModal}
onClose={()=>{setModal(false)}}
>
<Box id='auth-modal' className="absolute top-1/2 left-1/2 -translate-x-2/4 -translate-y-2/4 shadow-md p-4 bg-white">
<div className="flex flex-col gap-3 min-w-72 ">
<div id="modal-title">
<Typography variant="h6" component="h3">
{props.title}
</Typography>
</div>
<hr></hr>
<div id="modal-content">
{props.fetchFunc(req)}
</div>
<hr></hr>
<div id="modal-button">
{props.procedBtnFunc !== null &&
<Box className="flex flex-row gap-4 items-center justify-between">
<Button onClick={()=>{props.procedBtnFunc(req)}} variant='text'>proceed</Button>
</Box>
}
</div>
</div>
</Box>
</Modal>
)
}

const authentication_profile_container = document.getElementById('auth-modal');
if (authentication_profile_container){
const authentication_profile_root = createRoot(authentication_profile_container);
authentication_profile_root.render(<Auth title='Profile' btnId='onProfile' procedBtnFunc={null} fetchFunc={fetchProfile}></Auth>);
}
Now about the structure and functionality of this module. There is the main exported module - Auth. This is actually a modal window that will contain a user card, a login form, registration forms, and other forms.
There are also two types of functions: on* and fetch*. The first communicates with the server, and the second updates the modal window. It is not necessary to follow this pattern, but it makes the code easier to maintain.
Then there are the updateEmail and updateSession functions. They do exactly what they say: update the email address information and update the current session information. I also added additional functions such as:
  1. getCookie (name) - in case I want to get something and browser cookies.
  2. getCSRFToken () - almost every request requires a CSRF token, so I wrote a separate function
As for the global URLs object. This is not the entire list of available paths to the allauth API, but the minimum required for us. Of course, you can delete it and write all the URLs manually, but that's without me. Moving on.
We must not forget to change the Header component in src/components/header.js. When creating buttons in the site header, I create custom events to which I will connect the modal window, like this:
export default function Header() {
const header_buttons = document.getElementById('meta-header')
const btns = []
for ( const btn of header_buttons.children){
const ref = btn.firstElementChild.getAttribute('href')
if (btn.dataset.type == 'inner-link'){
btns.push(<Button color='primary'><a href={ref}>{btn.innerText}</a><LinkIcon className='mb-2' fontSize='small' /></Button>)
}
else if (btn.dataset.type == 'tutorial' ){
btns.push(<Button id='onTutorial' onClick={(e)=>{
const onTutorialEvent = new Event('onTutorial')
e.currentTarget.dispatchEvent(onTutorialEvent)
}} color='primary'><a href={ref}>{btn.innerText}</a><QuestionMarkIcon className='mb-2' fontSize='small'/></Button>)
}
else if (btn.dataset.type == 'profile' ){
btns.push(
<Button id='onProfile' onClick={(e)=>{
const onProfileEvent = new Event('onProfile')
e.currentTarget.dispatchEvent(onProfileEvent)
}} color='primary'><a href="#">{btn.innerText}</a><AccountBoxIcon className='mb-2' fontSize='small'/></Button>
)
}
}
Insert the highlighted lines of code

Connecting Allauth to the application

First, I would like to note the official documentation, it is complete and quite self-sufficient, although to understand how to use this library specifically on your site, you will need some time and study the official examples. Glory to them, it has some examples.
As always, let's start with installing the necessary packages. The django-allauth package is required, but django-allauth[socialaccount] is not, unless you need to use social networks as a provider for login.
pip install django-allauth
Next, you need to add the following allauth backend to settings.py:
AUTHENTICATION_BACKENDS = [
# Needed to login by username in Django admin, regardless of `allauth`
'django.contrib.auth.backends.ModelBackend',
# `allauth` specific authentication methods, such as login by email
'allauth.account.auth_backends.AuthenticationBackend',
]
Allauth is designed as an embedded django application, or rather a whole bunch of applications that need to be connected:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'rest_framework',
'Backend.apps.BackendConfig',
'Frontend.apps.FrontendConfig',
'Authentication.apps.AuthenticationConfig',
# BY ALLAUTH
'allauth',
'allauth.account',
]

MIDDLEWARE = (
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",

# BY ALLAUTH
"allauth.account.middleware.AccountMiddleware",
)
It will also be necessary to connect and configure the email backend. For the sole purpose of testing confirmation of sent mail and the ability to reset the password. I will connect the test backend, because it is simply faster and more visual. All messages will be written to files. In the future, we will, of course, change it to a normal email backend.
# BY ALLAUTH testing only
EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
EMAIL_FILE_PATH = BASE_DIR / 'emails'
Add these lines somewhere in settings.py
We are done in the settings.py file. All that remains is to add the paths and make migrations:
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from Backend import views

router = routers.DefaultRouter()
router.register(r'results', views.BackendModelView, 'result')

urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
path('', include('Frontend.urls')),
path('', include('Authentication.urls')),
# BY ALLAUTH
path('accounts/', include('allauth.urls')),
]
In the Website/urls.py file
python manage.py migrate
Make migration
After all these manipulations, go to the address http://localhost:8000/accounts/, you should see something like this:
an example of successfull connected django app allauth

Turn on an AllAuth "headless" mode

In fact, everything is ready, but not quite. I don't need to redirect to a separate page (this is not how my Django-React site will works). Ideally, I only need the API, and I will do the design myself. And to get access to the allauth API, I will need to tweak some more. Again, the official documentation is quite self-sufficient on this issue.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'rest_framework',
'Backend.apps.BackendConfig',
'Frontend.apps.FrontendConfig',
'Authentication.apps.AuthenticationConfig',
# BY ALLAUTH
'allauth',
'allauth.account',
'allauth.headless',
'allauth.mfa',
'allauth.usersessions',
]
Add the these apps
Also you need to add a new paths to alluth API.
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from Backend import views

router = routers.DefaultRouter()
router.register(r'results', views.BackendModelView, 'result')

urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
path('', include('Frontend.urls')),
path('', include('Authentication.urls')),
# BY ALLAUTH
path('accounts/', include('allauth.urls')),
path("_allauth/", include("allauth.headless.urls")),
]
In the Website/urls.py file
This is not necessary, but still. Since the entire frontend is on React, I don't need extra pages either, which means my site will use allauth in "headless only" mode. To do this, add these lines somewhere in settings.py:
HEADLESS_ONLY = True
HEADLESS_FRONTEND_URLS = {
"account_confirm_email": "http://localhost:8000/email-verify/{key}",
"account_reset_password_from_key": "http://localhost:8000/password-reset/{key}",
}
As you may have noticed, we have also added the HEADLESS_FRONTEND_URLS dictionary. It will be needed when we implement email confirmation and password reset, but for now, don't worry about it.

Connecting all together

From this point on, we can do the following: Stupidly write POST requests to the allauth API directly from React (we need literally 4 such requests: to log in /auth/login, to register /auth/signup, to confirm email /auth/email/verify, and to recover password /auth/password/request). In this case, no Django application will be required. Well, almost.
By the way, the entire headless API for making requests can be viewed here. I highly recommend it!!.
But you can do it differently: write a separate application, collect the necessary ModelForms there, render them on the server, and, if necessary, return them to the client. This is much longer but will allow you and me to better understand the internal workings of the allauth package, and moreover, this approach is more flexible in terms of design and fault tolerance of the application.
We will go the first way because React ... >﹏<

Making requests to server(Frontend)

User account page

We will start with the user card page. Of course, it will not be too big and complicated, rather the opposite. Minimum functionality, only necessary data. So far, without linking to other social networks.
function fetchProfile(req){
updateSession()

return (
<div>
{ session.is_authenticated ?
<Box>
<Typography>
You are logged in as <b>{session.username}</b>
</Typography>
<Box className="flex flex-row gap-4 items-center">
<Button onClick={()=>{fetchPasswordChange(req)}} variant='text'>Change password</Button>
<Button onClick={()=>{fetchPasswordReset(req)}} variant='text'>Reset password</Button>
</Box>
<Typography>
Your email are: <b>{session.email.address}</b> | {session.email.verified}
</Typography>
{ session.email.verified === 'not verified' &&
<Box className="flex flex-row gap-4 items-center">
<Button onClick={()=>{onRequestConfirmEmail(req, session.email.address)}} variant='text'>verify</Button>
</Box>
}
<Box className="flex flex-row gap-4 items-center">
<Button onClick={()=>{onSignout(req)}} variant='text'>Sign out</Button>
</Box>
</Box>
:
<Box>
<Typography>
You are not logged in
</Typography>
<Box className="flex flex-row gap-4 items-center">
<Button onClick={()=>{fetchLogin(req)}} variant='text'>Log in</Button>
<Typography>or</Typography>
<Button onClick={()=>{fetchSignup(req)}} variant='text'>Sign up</Button>
</Box>
</Box>
}
</div>
)
}
First, we get the session status, i.e., we find out whether it is a guest or a registered user. Then, if it is a registered user, we display all the information that may be useful to him (mail, mail type, name). And of course, we add buttons for interaction.
Something like this, registered user
Something like this, not registered user
Each button was linked to a modal window update function. And further we will analyze how this window changes and how the request will be sent to the server.

Form and registration request

For user registration I use 4 fields (one of which is optional) (+1 hidden field, we fill this field with a csrf token). I also change the title and the main button of the modal window.
function fetchSignup(req){
var container = req.modal.current.querySelector('#modal-content')
createRoot(container).render(
<form method='post' class="flex flex-col gap-5" style={{maxHeight: '500px', overflow: 'scroll'}}>
<FormControl style={{maxWidth: 'fit-content'}}>
<InputLabel htmlFor="username">Your username</InputLabel>
<Input class="w-fit" id="username"/>
</FormControl>
<FormControl style={{maxWidth: 'fit-content'}}>
<InputLabel htmlFor="email">Your email(optional)</InputLabel>
<Input type="email" id="email"/>
</FormControl>
<FormControl style={{maxWidth: 'fit-content'}}>
<InputLabel htmlFor="password">Your password</InputLabel>
<Input type='password' id="password"/>
</FormControl>
<FormControl style={{maxWidth: 'fit-content'}}>
<InputLabel htmlFor="password2">Your password(again)</InputLabel>
<Input type='password' id="password2"/>
</FormControl>
<List style={{maxWidth: '300px'}}>
<ListItem>
<ListItemText style={{color: "#6c6c24"}} primary="* Your password can’t be too similar to your other personal information." />
</ListItem>
<ListItem>
<ListItemText style={{color: "#6c6c24"}} primary="* Your password must contain at least 8 characters." />
</ListItem>
<ListItem>
<ListItemText style={{color: "#6c6c24"}} primary="* Your password can’t be a commonly used password." />
</ListItem>
<ListItem>
<ListItemText style={{color: "#6c6c24"}} primary="* Your password can’t be entirely numeric." />
</ListItem>
</List>
</form>
)
var container_btn = req.modal.current.querySelector('#modal-button')
createRoot(container_btn).render(
<Box className="flex flex-row gap-4 items-center justify-between">
<Button onClick={()=>{onSignup(req)}} variant='text'>proceed</Button>
</Box>
)
var container_title = req.modal.current.querySelector('#modal-title')
createRoot(container_title).render(
<Typography variant="h6" component="h3">
Sign up
</Typography>
)
}
When you click the PROCEED button, the form is sent. Well, that's briefly, but if expanded, then ... First, the spinner opens, then we collect all the necessary data from the form and check them. We make a POST request to the server along the path /_allauth/browser/v1/auth/signup, i.e., URLs.SIGNUP, having previously left the CSRF token and the required response format in the request header. Then, regardless of the response, we close the form and leave a message to the user about the success of the request.
function onSignup(req){
// Do POST request to server
// Try to sign up
Wait()
var form_data = new FormData()
var password1 = document.querySelector('#password').value
var password2 = document.querySelector('#password2').value
form_data.append('username', document.querySelector('#username').value)
form_data.append('email', document.querySelector('#email').value)
form_data.append('password', password1)
form_data.append('csrfmiddlewaretoken', getCSRFToken())
if (password2 !== password1) {
Msg('Password does not match.', 'error')
return
}
axios.post(URLs.SIGNUP, form_data, {
headers: {
"accept": "application/json",
"X-CSRFToken": getCSRFToken(),
'Content-Type': "application/json",
}
})
.then(data => {
StopWait(`Welcome ${document.querySelector('#username').value}.`, 'success')
updateSession()
req.setModal(false);
})
.catch(err => {
StopWait(`${err.response.data.errors[0].message}`, 'error')
req.setModal(false);
});
}

Form and login request

Login form, simpler than registration. It only requires a username and password.
We create three root elements and change the form itself, the button and the form title accordingly.
function fetchLogin(req){
var container = req.modal.current.querySelector('#modal-content')
createRoot(container).render(
<form method='post' class="flex flex-col gap-5">
<FormControl>
<InputLabel htmlFor="username">Your username</InputLabel>
<Input id="username"/>
</FormControl>
<FormControl>
<InputLabel htmlFor="password">Your password</InputLabel>
<Input type='password' id="password"/>
</FormControl>
<Link href="#" onClick={()=>{fetchPasswordReset(req)}}><Typography>Forgot your password?</Typography></Link>
<FormControl>
<FormControlLabel control={<Checkbox id="remember-me"/>} label="Remember me?" />
</FormControl>
</form>
)
var container_btn = req.modal.current.querySelector('#modal-button')
createRoot(container_btn).render(
<Box className="flex flex-row gap-4 items-center justify-between">
<Button onClick={()=>{onLogin(req)}} variant='text'>proceed</Button>
</Box>
)
var container_title = req.modal.current.querySelector('#modal-title')
createRoot(container_title).render(
<Typography variant="h6" component="h3">
Log in
</Typography>
)
}
We send the form to the address /_allauth/browser/v1/auth/login, i.e. URLs.LOGIN.
function onLogin(req){
Wait()
var form_data = new FormData()
form_data.append('username', document.querySelector('#username').value)
form_data.append('password', document.querySelector('#password').value)
form_data.append('remember', document.querySelector('#remember-me').checked)
form_data.append('csrfmiddlewaretoken', getCSRFToken())
axios.post(URLs.LOGIN, form_data, {
headers: {
"accept": "application/json",
"X-CSRFToken": getCSRFToken(),
'Content-Type': "application/json",
}
})
.then(data => {
StopWait(`Welcome ${document.querySelector('#username').value}.`, 'success')
updateSession()
req.setModal(false);
})
.catch(err => {
StopWait(`${err.response.data.errors[0].message}`, 'error')
req.setModal(false);
});
}

Form and request to change password

To change the password we need a separate form. In the fetchPasswordChange function, we change the form, the title and the confirmation button. I also ask to enter the new password twice, just in case...
function fetchPasswordChange(req){
var container = req.modal.current.querySelector('#modal-content')
createRoot(container).render(
<form method='post' class="flex flex-col gap-5">
<FormControl style={{maxWidth: 'fit-content'}}>
<InputLabel htmlFor="current_password">Current password</InputLabel>
<Input type="password" id="current_password"/>
</FormControl>
<FormControl style={{maxWidth: 'fit-content'}}>
<InputLabel htmlFor="new_password1">New password</InputLabel>
<Input type="password" id="new_password1"/>
</FormControl>
<FormControl style={{maxWidth: 'fit-content'}}>
<InputLabel htmlFor="new_password2">New password(again)</InputLabel>
<Input type="password" id="new_password2"/>
</FormControl>
</form>
)
var container_btn = req.modal.current.querySelector('#modal-button')
createRoot(container_btn).render(
<Box className="flex flex-row gap-4 items-center justify-between">
<Button onClick={()=>{onPasswordChange(req)}} variant='text'>change</Button>
</Box>
)
var container_title = req.modal.current.querySelector('#modal-title')
createRoot(container_title).render(
<Typography variant="h6" component="h3">
Change password
</Typography>
)
}
Before sending a request to change the password, you need to check whether the new passwords match each other. Then make a POST request to /_allauth/browser/v1/account/password/change or URLs.CHANGE_PASSWORD.
function onPasswordChange(req){
var form_data = new FormData()
var password1 = document.querySelector('#new_password1').value
var password2 = document.querySelector('#new_password2').value
form_data.append('current_password', document.querySelector('#current_password').value)
form_data.append('new_password', password1)
form_data.append('csrfmiddlewaretoken', getCSRFToken())
if (password2 !== password1) {
Msg('Password does not match.', 'error')
return
}
axios.post(URLs.CHANGE_PASSWORD, form_data, {
headers: {
"accept": "application/json",
"X-CSRFToken": getCSRFToken(),
'Content-Type': "application/json",
}
})
.then(data => {
StopWait("You had successfully change a password", 'success')
updateSession()
req.setModal(false);
})
.catch(err => {
StopWait(`${err.response.data.errors[0].message}`, 'error')
req.setModal(false);
});
}

Form and password reset request

We update the modal window, i.e. the title, the form and the button itself, of course. To send a password recovery request, you only need to provide an email address.
function fetchPasswordReset(req){
var container = req.modal.current.querySelector('#modal-content')
createRoot(container).render(
<form method='post' class="flex flex-col gap-5">
<Typography> You will recieve a confirmation message.</Typography>
<FormControl style={{maxWidth: 'fit-content'}}>
<InputLabel htmlFor="password_email">Your email</InputLabel>
<Input type="email" id="password_email"/>
</FormControl>
</form>
)
var container_btn = req.modal.current.querySelector('#modal-button')
createRoot(container_btn).render(
<Box className="flex flex-row gap-4 items-center justify-between">
<Button onClick={()=>{onRequestPasswordReset(req)}} variant='text'>send</Button>
</Box>
)
var container_title = req.modal.current.querySelector('#modal-title')
createRoot(container_title).render(
<Typography variant="h6" component="h3">
Password reset
</Typography>
)
}
So how does it work? The user has forgotten his password and wants to recover it. To do this, he sends a POST request to /_allauth/browser/v1/auth/password/request or URLs.REQUEST_PASSWORD_RESET. The server receives this request and sends an email to the specified address.
function onRequestPasswordReset(req){
Wait()
var form_data = new FormData()
var email = document.querySelector('#password_email').value
form_data.append('email', email)
axios.post(URLs.REQUEST_PASSWORD_RESET, form_data, {
headers: {
"accept": "application/json",
"X-CSRFToken": getCSRFToken(),
'Content-Type': "application/json",
}
}).then((data)=>{
StopWait(`Successfully send an email to ${email}`, 'success')
req.setModal(false);
}).catch((err)=>{
StopWait(`${err.response.data.errors[0].message}`, 'error')
req.setModal(false);
})


}

Password recovery request function
The message has been sent. The message will contain a link to a page with a key in the address. You need to follow this address to get to a page like this.
Out of the box, django-allauth does not provide a page for processing requests for password recovery or email verification (UNLESS, OF COURSE, THIS IS NOT HEADLESS MODE). Otherwise, there will be a redirect to a template page provided in normal mode.
On this page, you need to fill out this form. That is, enter a new password. It would probably be better if I also asked for password confirmation, but this will do. In fact, the form should be sent with two fields:
  1. new password
  2. generated key
function onConfirmReset(key, container){
var form_data = new FormData()
var password1 = document.querySelector('#password1').value
var password2 = document.querySelector('#password2').value
if (password2 !== password1) {
Msg('Password does not match.', 'error')
return
}
form_data.append('key', key)
form_data.append('password', password1)
axios.post(URLs.RESET_PASSWORD, form_data, {
headers: {
"accept": "application/json",
"X-CSRFToken": getCSRFToken(),
'Content-Type': "application/json",
}
}).then((data) => {
Msg('You have successfully changed your password.', 'success')
createRoot(container.current).render(
<Typography>You have successfully changed your password.</Typography>
)
}).catch((err) => {
if (err.response.status === 401){
Msg('You have successfully changed your password.', 'success')
createRoot(container.current).render(
<Box className="flex flex-col gap-6 items-center justify-between">
<Typography>You have successfully changed your password.</Typography>
</Box>
)
}else{
var errors = err.response.data.errors
const lines = []
errors.forEach((error) =>{
lines.push(<Typography>{error.message}</Typography>)
})
createRoot(container.current).render(
<Box className="flex flex-col gap-6 items-center justify-between">
{lines}
</Box>
)
}
});
}
In this case, I did not separate the on* and fetch* functions because this is a separate component and the component is quite small. Place this function in ./components/PasswordReset.js
In order for this function to make sense at all, I created a separate component called PasswordReset. I created a component file PasswordReset.js in components and connected it to index.js:
import Header from './components/Header';
import Footer from './components/Footer';
import AppSettings from './components/AppSettings';
import AppUtils from './components/AppUtils';
import AppActions from './components/AppActions';
import AppQueries from './components/AppQueries';
import Message from './components/Msg';
import Hint from './components/Tutorial';
import Auth from './components/Auth';
import PasswordReset from './components/PasswordReset';
The full source code example of component:
import * as React from 'react';
import axios from "axios";
import { createRoot } from 'react-dom/client';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button'
import {URLs, getCSRFToken} from './Auth'
import FormControl from '@mui/material/FormControl';
import Input from '@mui/material/Input';
import InputLabel from '@mui/material/InputLabel';
import { Wait, StopWait, Msg } from './Waiter';


function onConfirmReset(key, container){
var form_data = new FormData()
var password1 = document.querySelector('#password1').value
var password2 = document.querySelector('#password2').value
if (password2 !== password1) {
Msg('Password does not match.', 'error')
return
}
form_data.append('key', key)
form_data.append('password', password1)
axios.post(URLs.RESET_PASSWORD, form_data, {
headers: {
"accept": "application/json",
"X-CSRFToken": getCSRFToken(),
'Content-Type': "application/json",
}
}).then((data) => {
Msg('You have successfully changed your password.', 'success')
createRoot(container.current).render(
<Typography>You have successfully changed your password.</Typography>
)
}).catch((err) => {
if (err.response.status === 401){
Msg('You have successfully changed your password.', 'success')
createRoot(container.current).render(
<Box className="flex flex-col gap-6 items-center justify-between">
<Typography>You have successfully changed your password.</Typography>
</Box>
)
}else{
var errors = err.response.data.errors
const lines = []
errors.forEach((error) =>{
lines.push(<Typography>{error.message}</Typography>)
})
createRoot(container.current).render(
<Box className="flex flex-col gap-6 items-center justify-between">
{lines}
</Box>
)
}
});
}

export default function PasswordReset(){
const key = document.querySelector('#password-reset-block').dataset.key
const containerRef = React.useRef(null)
return(
<Box ref={containerRef} className="w-full h-full content-center">
<Box className="flex flex-col gap-6 items-center justify-between">
<Typography>To finish password reset procedure fill the form bellow</Typography>
<FormControl style={{maxWidth: 'fit-content'}}>
<InputLabel htmlFor="password1">Your new password</InputLabel>
<Input type='password' id="password1"/>
</FormControl>
<FormControl style={{maxWidth: 'fit-content'}}>
<InputLabel htmlFor="password2">Your new password(again)</InputLabel>
<Input type='password' id="password2"/>
</FormControl>
<Button onClick={()=>{onConfirmReset(key, containerRef)}} variant='text'>proceed</Button>
</Box>
</Box>
)
}

const password_reset_container = document.getElementById('password-reset-block')
if (password_reset_container){
const password_reset_root = createRoot(password_reset_container);
password_reset_root.render(<PasswordReset></PasswordReset>);
}
That's not all. You will also need to create an html template on the server side and add the corresponding paths to urls.py

Form and request for email confirmation

My authentication system also supports email validation. There is no particular point in this for now, but in the future it will be quite useful if I, for example, want to know whether this is a valid email address or not.
We must send a PUT request to /_allauth/browser/v1/account/email or URLs.EMAIL
function onRequestConfirmEmail(req, email){
Wait()
var form_data = new FormData()
form_data.append('email', email)
axios.put(URLs.EMAIL, form_data, {
headers: {
"accept": "application/json",
"X-CSRFToken": getCSRFToken(),
'Content-Type': "application/json",
}
}).then( data =>{
StopWait(`Successfully send an email to ${email}`, 'success')
}).catch( err =>{
if (err.response.status !== 403){
StopWait(`${err.response.data.errors[0].message}`, 'error')
}
})
}
A letter with a link to the page with the key will be sent to the specified email. After going to this page, the user will see the following:
Where you only need to click on a button. Sending the confirmation form is done by this function:
function onConfirmEmail(key, container){
var form_data = new FormData()
form_data.append('key', key)
axios.post(URLs.VERIFY_EMAIL, form_data, {
headers: {
"accept": "application/json",
"X-CSRFToken": getCSRFToken(),
'Content-Type': "application/json",
}
}).then((data) => {
createRoot(container.current).render(
<Typography>You successfully verified your email.</Typography>
)
}).catch((err) => {
if (err.response.status === 401){
createRoot(container.current).render(
<Box className="flex flex-col gap-6 items-center justify-between">
<Typography>You successfully verified your email.</Typography>
</Box>
)
}
var errors = err.response.data.errors
const lines = []
errors.forEach((error) =>{
lines.push(<Typography>{error.message}</Typography>)
})
createRoot(container.current).render(
<Box className="flex flex-col gap-6 items-center justify-between">
{lines}
</Box>
)
});
}
In this case, I did not separate the on* and fetch* functions because this is a separate component and the component is quite small. Place this function in ./components/EmailVerify.js
It sends the key to the server to confirm the email and returns the corresponding message about the verification status.
Just like in the case of password reset, I created a separate component and connected it to index.js:
import Header from './components/Header';
import Footer from './components/Footer';
import AppSettings from './components/AppSettings';
import AppUtils from './components/AppUtils';
import AppActions from './components/AppActions';
import AppQueries from './components/AppQueries';
import Message from './components/Msg';
import Hint from './components/Tutorial';
import Auth from './components/Auth';
import EmailVerify from './components/EmailVerify';
import PasswordReset from './components/PasswordReset';
Here is a full source code of component, EmailVerify:
import * as React from 'react';
import axios from "axios";
import { createRoot } from 'react-dom/client';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button'
import {URLs, getCSRFToken} from './Auth'
import { Wait, StopWait, Msg } from './Waiter';


function onConfirmEmail(key, container){
var form_data = new FormData()
form_data.append('key', key)
axios.post(URLs.VERIFY_EMAIL, form_data, {
headers: {
"accept": "application/json",
"X-CSRFToken": getCSRFToken(),
'Content-Type': "application/json",
}
}).then((data) => {
createRoot(container.current).render(
<Typography>You successfully verified your email.</Typography>
)
}).catch((err) => {
if (err.response.status === 401){
createRoot(container.current).render(
<Box className="flex flex-col gap-6 items-center justify-between">
<Typography>You successfully verified your email.</Typography>
</Box>
)
}
var errors = err.response.data.errors
const lines = []
errors.forEach((error) =>{
lines.push(<Typography>{error.message}</Typography>)
})
createRoot(container.current).render(
<Box className="flex flex-col gap-6 items-center justify-between">
{lines}
</Box>
)
});
}

export default function EmailVerify(){
const key = document.querySelector('#email-verify-block').dataset.key
const containerRef = React.useRef(null)
return(
<Box ref={containerRef} className="w-full h-full content-center">
<Box className="flex flex-col gap-6 items-center justify-between">
<Typography>To finish email verification click the button below</Typography>
<Button onClick={()=>{onConfirmEmail(key, containerRef)}} variant='text'>proceed</Button>
</Box>
</Box>
)
}

const email_verify_container = document.getElementById('email-verify-block')
if (email_verify_container){
const email_verify_root = createRoot(email_verify_container);
email_verify_root.render(<EmailVerify></EmailVerify>);
}
That's not all. You will also need to create an html template on the server side and add the appropriate paths to urls.py

Form and logout request

And finally, logout of the current session. A DELETE request is send to /_allauth/browser/v1/auth/session or URLs.LOGOUT. An error with code 401 will be returned, so we logout in the catch block.
function onSignout(req){
Wait()
axios.delete(URLs.LOGOUT, {
headers: {
"accept": "application/json",
"X-CSRFToken": getCSRFToken(),
'Content-Type': "application/json",
}
}).catch(error =>{
StopWait('You have beed log out.')
updateSession()
req.setModal(false)
})
}

Setting up routes and views for email verification and password reset

We are almost done. All that remains is to set up the Backend for email verification and password change. Create a new django application, if you don't know how, see here. Name it Authentication and of course connect it to the site project.
Next, create two templates in templates/Authentication, email_verify.html and password_reset.html
Contents of email_verify.html file:
{% extends "Frontend/base.html" %}

{% block main %}
<div id="email-verify-block" class="w-full h-full" data-key="{{key}}">
</div>
{% endblock%}
Contents of password_reset.html file:
{% extends "Frontend/base.html" %}

{% block main %}
<div id="password-reset-block" class="w-full h-full" data-key="{{key}}">
</div>
{% endblock%}
As you can see, they are almost identical. In principle, I could have made one common template, but I thought that perhaps, in the future, it might be necessary to somehow develop them separately. Now let's add two new urls to Authentication/urls.py:
from django.urls import path
from .views import email_verify, password_reset

urlpatterns = [
path('email-verify/<str:key>', email_verify, name='email_verify'),
path('password-reset/<str:key>', password_reset, name='password_reset'),
]
Note that the paths match those I specified in settings.py earlier. All that remains is to create the corresponding views for these templates, email_verify and password_reset:
from django.shortcuts import render

def email_verify(request, key):
context = {
'key': key,
}
return render(request, 'Authentication/email_verify.html', context)

def password_reset(request, key):
context = {
'key': key,
}
return render(request, 'Authentication/password_reset.html', context)
Everything is pretty straightforward here, we get the key from the URL, save it to the context when rendering and insert the key as the value of the data-key attribute in the containers. This attribute will then be used by React to send callbacks.

Conclusion

I have finished adding a system for authentication and registration of my guests on the site. Of course, you can also add login and registration via social networks or based on sent confirmation keys. All this will come later and perhaps in other projects, but definitely not in this article, it has already turned out to be too big.
You can download the current and full version of the project site here.
In the next article, we will work on translating our site into other languages, but for now, see you soon. ( ̄︶ ̄)↗


Comments

(0)
captcha
Send
It's empty now. Be the first (o゚v゚)ノ