27
FullStack React & Django Authentication : Django REST ,TypeScript, Axios, Redux & React Router
As a full-stack developer, understand how to build an authentication system with backend technology and manage the authentication flow with a frontend technology is crucial.
In this tutorial, we'll together build an authentication system using React and Django.
We'll be using Django and Django Rest to build the API and create authentication endpoints. And after, set up a simple login and profile page with React and Tailwind, using Redux and React router by the way.
We'll be using Django and Django Rest to build the API and create authentication endpoints. And after, set up a simple login and profile page with React and Tailwind, using Redux and React router by the way.
First of all, let's set up the project. Feel free to use your favorite python environment management tool. I’ll be using
virtualenv
here.virtualenv --python=/usr/bin/python3.8 venv
source venv/bin/activate
pip install django djangorestframework djangorestframework-simplejwt
django-admin startproject CoreRoot .
django-admin startapp core
__init__.py
and apps.py
.core
to the INSTALLED_APPS :
# CoreRoot/settings.py
...
'django.contrib.messages',
'django.contrib.staticfiles',
'core'
We can now create the user application and start adding features.
cd core && python ../manage.py startapp user
# CoreRoot/settings.py
...
'rest_framework',
'core',
'core.user'
For this configuration to work, you'll need to modify the name of the app in core/user/apps.py
# core/user/apps.py
from django.apps import AppConfig
class UserConfig(AppConfig):
name = 'core.user'
label = 'core_user'
And also the
__init__.py
file in core/user
directory.# core/user/__init__.py
default_app_config = 'core.user.apps.UserConfig'
Django comes with a built-in authentication system model which fits most of the user cases and is quite safe. But most of the time, we need to do rewrite it to adjust the needs of our project. You may add others fields like bio, birthday, or other things like that.
A Custom User Model is a new user that inherits from
But it’s important to note that these modifications require special care and updates of some references through the
AbstractBaseUser
. But we’ll also rewrite the UserManager
to customize the creation of a user in the database.But it’s important to note that these modifications require special care and updates of some references through the
settings.py
.# core/user/models.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
class UserManager(BaseUserManager):
def create_user(self, username, email, password=None, **kwargs):
"""Create and return a `User` with an email, phone number, username and password."""
if username is None:
raise TypeError('Users must have a username.')
if email is None:
raise TypeError('Users must have an email.')
user = self.model(username=username, email=self.normalize_email(email))
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, password):
"""
Create and return a `User` with superuser (admin) permissions.
"""
if password is None:
raise TypeError('Superusers must have a password.')
if email is None:
raise TypeError('Superusers must have an email.')
if username is None:
raise TypeError('Superusers must have an username.')
user = self.create_user(username, email, password)
user.is_superuser = True
user.is_staff = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(db_index=True, max_length=255, unique=True)
email = models.EmailField(db_index=True, unique=True, null=True, blank=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
objects = UserManager()
def __str__(self):
return f"{self.email}"
Now what we'll do next is specify to Django to use this new User model as the
AUTH_USER_MODEL
.# CoreRoot/settings.py
...
AUTH_USER_MODEL = 'core_user.User'
...
The next step when working with Django & Django Rest after creating a model is to write a serializer.
Serializer allows us to convert complex Django complex data structures such as
Serializer allows us to convert complex Django complex data structures such as
querysets
or model instances in Python native objects that can be easily converted JSON/XML format, but Serializer also serializes JSON/XML to naive Python.# core/user/serializers.py
from core.user.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'is_active', 'created', 'updated']
read_only_field = ['is_active', 'created', 'updated']
And the viewset. A viewset is a class-based view, able to handle all of the basic HTTP requests: GET, POST, PUT, DELETE without hard coding any of the logic. And if you have specific needs, you can overwrite those methods.
# core/user/viewsets.py
from core.user.serializers import UserSerializer
from core.user.models import User
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework import filters
class UserViewSet(viewsets.ModelViewSet):
http_method_names = ['get']
serializer_class = UserSerializer
permission_classes = (IsAuthenticated,)
filter_backends = [filters.OrderingFilter]
ordering_fields = ['updated']
ordering = ['-updated']
def get_queryset(self):
if self.request.user.is_superuser:
return User.objects.all()
def get_object(self):
lookup_field_value = self.kwargs[self.lookup_field]
obj = User.objects.get(lookup_field_value)
self.check_object_permissions(self.request, obj)
return obj
REST framework provides several authentication schemes out of the box, but we can also implement our custom schemes. We'll use authentication using JWT tokens.
For this purpose, we'll use the
Add
For this purpose, we'll use the
djangorestframework-simplejwt
to implement an access/refresh logic.Add
rest_framework_simplejwt.authentication.JWTAuthentication
to the list of authentication classes in settings.py
:# CoreRoot/settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
)
}
The Simple JWT library comes with two useful routes:
And since we are using viewsets, there is a problem with consistency.
But here’s the solution :
But here’s the solution :
djangorestframework-simplejwt
contributors, it’s very simple to read the code, understand how it works and extend it successfully.auth
in core
.serializer.py
which will contain the login and register serializers.
# core/auth/serializers.py
from rest_framework import serializers
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.settings import api_settings
from django.contrib.auth.models import update_last_login
from django.core.exceptions import ObjectDoesNotExist
from core.user.serializers import UserSerializer
from core.user.models import User
class LoginSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
data = super().validate(attrs)
refresh = self.get_token(self.user)
data['user'] = UserSerializer(self.user).data
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)
if api_settings.UPDATE_LAST_LOGIN:
update_last_login(None, self.user)
return data
class RegisterSerializer(UserSerializer):
password = serializers.CharField(max_length=128, min_length=8, write_only=True, required=True)
email = serializers.EmailField(required=True, write_only=True, max_length=128)
class Meta:
model = User
fields = ['id', 'username', 'email', 'password', 'is_active', 'created', 'updated']
def create(self, validated_data):
try:
user = User.objects.get(email=validated_data['email'])
except ObjectDoesNotExist:
user = User.objects.create_user(**validated_data)
return user
Then, we can write the viewsets.
# core/auth/viewsets
from rest_framework.response import Response
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import AllowAny
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.exceptions import TokenError, InvalidToken
from core.auth.serializers import LoginSerializer, RegistrationSerializer
class LoginViewSet(ModelViewSet, TokenObtainPairView):
serializer_class = LoginSerializer
permission_classes = (AllowAny,)
http_method_names = ['post']
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
except TokenError as e:
raise InvalidToken(e.args[0])
return Response(serializer.validated_data, status=status.HTTP_200_OK)
class RegistrationViewSet(ModelViewSet, TokenObtainPairView):
serializer_class = RegisterSerializer
permission_classes = (AllowAny,)
http_method_names = ['post']
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
refresh = RefreshToken.for_user(user)
res = {
"refresh": str(refresh),
"access": str(refresh.access_token),
}
return Response({
"user": serializer.data,
"refresh": res["refresh"],
"token": res["access"]
}, status=status.HTTP_201_CREATED)
class RefreshViewSet(viewsets.ViewSet, TokenRefreshView):
permission_classes = (AllowAny,)
http_method_names = ['post']
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
except TokenError as e:
raise InvalidToken(e.args[0])
return Response(serializer.validated_data, status=status.HTTP_200_OK)
The next step is to register the routes.
Create a file
Create a file
routers.py
in the core
directory.# core/routers.py
from rest_framework.routers import SimpleRouter
from core.user.viewsets import UserViewSet
from core.auth.viewsets import LoginViewSet, RegistrationViewSet, RefreshViewSet
routes = SimpleRouter()
# AUTHENTICATION
routes.register(r'auth/login', LoginViewSet, basename='auth-login')
routes.register(r'auth/register', RegistrationViewSet, basename='auth-register')
routes.register(r'auth/refresh', RefreshViewSet, basename='auth-refresh')
# USER
routes.register(r'user', UserViewSet, basename='user')
urlpatterns = [
*routes.urls
]
And the last step, we'll include the
routers.urls
in the standard list of URL patterns in CoreRoot
.# CoreRoot/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('api/', include(('core.routers', 'core'), namespace='core-api')),
]
The User endpoints, login, and register viewsets are ready. Don't forget to run migrations and start the server and test the endpoints.
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
If everything is working fine, let's create a user with an HTTP Client by requesting
localhost:8000/api/auth/register/
. I'll be using Postman but feel free to use any client.{
"email": "testuser@yopmail.com",
"password": "12345678",
"username": "testuser"
}
There are generally two ways to connect Django to your frontend :
The most used pattern is the first one, and we'll focus on it because we have already our token authentication system available.
Make sure you have the latest version of
Make sure you have the latest version of
create-react-app
in your machine.yarn create react-app react-auth-app --template typescript
cd react-auth-app
yarn start
Then open http://localhost:3000/ to see your app.
But, we'll have a problem. If we try to make a request coming from another domain or origin (here from our frontend with the webpack server), the web browser will throw an error related to the Same Origin Policy. CORS stands for Cross-Origin Resource Sharing and allows your resources to be accessed on other domains.
Cross-Origin Resource Sharing or CORS allows client applications to interface with APIs hosted on different domains by enabling modern web browsers to bypass the Same-origin Policy which is enforced by default.
Let's enable CORS with Django REST by using
Cross-Origin Resource Sharing or CORS allows client applications to interface with APIs hosted on different domains by enabling modern web browsers to bypass the Same-origin Policy which is enforced by default.
Let's enable CORS with Django REST by using
django-cors-headers
.pip install django-cors-headers
If the installation is done, go to your settings.py file and add the package in
INSTALLED_APPS
and the middleware.INSTALLED_APPS = [
...
'corsheaders',
...
]
MIDDLEWARE = [
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
And add these lines at the end of the
settings.py
file.CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://127.0.0.1:3000"
]
We are good now. Let's continue with the front end by adding libraries we'll be using.
First of all, let's add tailwind and make a basic configuration for the project.
yarn add tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
Since Create React App doesn’t let you override the
PostCSS
configuration natively, we also need to install CRACO to be able to configure Tailwind.yarn add @craco/craco
Once it's installed, modify these lines in the
package.json
file. Replace react-
scripts
by craco
."scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
}
Next, we'll create a craco config file in the root of the project, and add
tailwindcss
and autoprefixer
as plugins.//craco.config.js
module.exports = {
style: {
postcss: {
plugins: [require("tailwindcss"), require("autoprefixer")],
},
},
};
Next, we need to create a configuration file for tailwind.
Use
Use
npx tailwindcss-cli@latest init
to generate tailwind.config.js
file containing the minimal configuration for tailwind.module.exports = {
purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
};
The last step will be to include tailwind in the
index.css
file./*src/index.css*/
@tailwind base;
@tailwind components;
@tailwind utilities;
We are done with the tailwind configuration.
Let's quickly create the Login Page and the Profile Page.
// ./src/pages/Login.tsx
import React, { useState } from "react";
import * as Yup from "yup";
import { useFormik } from "formik";
import { useDispatch } from "react-redux";
import axios from "axios";
import { useHistory } from "react-router";
function Login() {
const [message, setMessage] = useState("");
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();
const history = useHistory();
const handleLogin = (email: string, password: string) => {
//
};
const formik = useFormik({
initialValues: {
email: "",
password: "",
},
onSubmit: (values) => {
setLoading(true);
handleLogin(values.email, values.password);
},
validationSchema: Yup.object({
email: Yup.string().trim().required("Le nom d'utilisateur est requis"),
password: Yup.string().trim().required("Le mot de passe est requis"),
}),
});
return (
<div className="h-screen flex bg-gray-bg1">
<div className="w-full max-w-md m-auto bg-white rounded-lg border border-primaryBorder shadow-default py-10 px-16">
<h1 className="text-2xl font-medium text-primary mt-4 mb-12 text-center">
Log in to your account 🔐
</h1>
<form onSubmit={formik.handleSubmit}>
<div className="space-y-4">
<input
className="border-b border-gray-300 w-full px-2 h-8 rounded focus:border-blue-500"
id="email"
type="email"
placeholder="Email"
name="email"
value={formik.values.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.errors.email ? <div>{formik.errors.email} </div> : null}
<input
className="border-b border-gray-300 w-full px-2 h-8 rounded focus:border-blue-500"
id="password"
type="password"
placeholder="Password"
name="password"
value={formik.values.password}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.errors.password ? (
<div>{formik.errors.password} </div>
) : null}
</div>
<div className="text-danger text-center my-2" hidden={false}>
{message}
</div>
<div className="flex justify-center items-center mt-6">
<button
type="submit"
disabled={loading}
className="rounded border-gray-300 p-2 w-32 bg-blue-700 text-white"
>
Login
</button>
</div>
</form>
</div>
</div>
);
}
export default Login;
Here's a preview :

And the profile page :
// ./src/pages/Profile.tsx
import React from "react";
import { useDispatch } from "react-redux";
import { useHistory } from "react-router";
const Profile = () => {
const dispatch = useDispatch();
const history = useHistory();
const handleLogout = () => {
//
};
return (
<div className="w-full h-screen">
<div className="w-full p-6">
<button
onClick={handleLogout}
className="rounded p-2 w-32 bg-red-700 text-white"
>
Deconnexion
</button>
</div>
<div className="w-full h-full text-center items-center">
<p className="self-center my-auto">Welcome</p>
</div>
</div>
);
};
export default Profile;
And here's the preview :

And the final step, we'll be making requests on an API. It's a good practice to configure environment variables. Fortunately, React allows us to make basic environment configurations.
Create a
Create a
.env
file at the root of the project and put this here../.env
REACT_APP_API_URL=localhost:8000/api
Redux is a library to manage the global state in our application.
Here, we want the user to log in and go to the Profile Page. It will only work if the login is correct.
But that's not all: if the user has no active session -meaning that the refresh is expired or there is no trace of this user account or tokens in the storage of the frontend - he is directly redirected to the login page.
Here, we want the user to log in and go to the Profile Page. It will only work if the login is correct.
But that's not all: if the user has no active session -meaning that the refresh is expired or there is no trace of this user account or tokens in the storage of the frontend - he is directly redirected to the login page.
To make things simple, here's what we're going to do:
redux-toolkit
to save, account state, and tokens when the user signs in. We'll also write an action for logout.First of all, let's add the dependencies we need to configure the store.
yarn add @reduxjs/toolkit redux react-redux redux-persist
Then, create a folder named
Add in this directory another folder named
With Redux, a slice is a collection of reducer logic and actions for a single feature of our app.
But before adding content to this file, we need to write the interface for the user account.
store
in src
.Add in this directory another folder named
slices
and create in this directory a file named auth.ts
.With Redux, a slice is a collection of reducer logic and actions for a single feature of our app.
But before adding content to this file, we need to write the interface for the user account.
// ./src/types.ts
export interface AccountResponse {
user: {
id: string;
email: string;
username: string;
is_active: boolean;
created: Date;
updated: Date;
};
access: string;
refresh: string;
}
And now, we can write the authentication slice
authSlice
.// ./src/store/slices/auth.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { AccountResponse } from "../../types";
type State = {
token: string | null;
refreshToken: string | null;
account: AccountResponse | null;
};
const initialState: State = { token: null, refreshToken: null, account: null };
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
setAuthTokens(
state: State,
action: PayloadAction<{ token: string; refreshToken: string }>
) {
state.refreshToken = action.payload.refreshToken;
state.token = action.payload.token;
},
setAccount(state: State, action: PayloadAction<AccountResponse>) {
state.account = action.payload;
},
logout(state: State) {
state.account = null;
state.refreshToken = null;
state.token = null;
},
},
});
export default authSlice;
Now, move inside the store directory and create a file named
index.ts
. And add the following content.// ./src/store/index.ts
import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit";
import { combineReducers } from "redux";
import {
FLUSH,
PAUSE,
PERSIST,
persistReducer,
persistStore,
PURGE,
REGISTER,
REHYDRATE,
} from "redux-persist";
import storage from "redux-persist/lib/storage";
import authSlice from "./slices/auth";
const rootReducer = combineReducers({
auth: authSlice.reducer,
});
const persistedReducer = persistReducer(
{
key: "root",
version: 1,
storage: storage,
},
rootReducer
);
const store = configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
export const persistor = persistStore(store);
export type RootState = ReturnType<typeof rootReducer>;
export default store;
Now the store has been created, we need to make the
store
accessible for all components by wrapping <App />
(top-level-component) in :// ./src/App.tsx
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { Login, Profile } from "./pages";
import store, { persistor } from "./store";
import { PersistGate } from "redux-persist/integration/react";
import { Provider } from "react-redux";
import ProtectedRoute from "./routes/ProtectedRoute";
export default function App() {
return (
<Provider store={store}>
<PersistGate persistor={persistor} loading={null}>
<Router>
<div>
<Switch>
<Route exact path="/login" component={Login} />
<ProtectedRoute exact path="/" component={Profile} />
</Switch>
</div>
</Router>
</PersistGate>
</Provider>
);
}
The store is accessible by all components in our application now. The next step is to build a
<ProtectedRoute />
component to help us hide pages that require sessions from the other ones.We'll build the
React Router is a standard library for routing in React. It enables the navigation among views of various components in a React Application, allows changing the browser URL, and keeps the UI in sync with the URL.
In our application, If the user tries to access a protected page, we'll be redirected to the Login Page.
<ProtectedRoute />
component using React Router. React Router is a standard library for routing in React. It enables the navigation among views of various components in a React Application, allows changing the browser URL, and keeps the UI in sync with the URL.
In our application, If the user tries to access a protected page, we'll be redirected to the Login Page.
cd src & mkdir routes
cd routes
In the routes, directory creates a file named
ProtectedRoute.tsx
, and write this :// ./src/routes/ProtectedRoute.tsx
import React from "react";
import { Redirect, Route, RouteProps } from "react-router";
import { useSelector } from "react-redux";
import { RootState } from "../store";
const ProtectedRoute = (props: RouteProps) => {
const auth = useSelector((state: RootState) => state.auth);
if (auth.account) {
if (props.path === "/login") {
return <Redirect to={"/"} />;
}
return <Route {...props} />;
} else if (!auth.account) {
return <Redirect to={"/login"} />;
} else {
return <div>Not found</div>;
}
};
export default ProtectedRoute;
The first step here is to get the global state of
If there is an account object, that means that there is an active session.
Then, we use this state to check if we have to redirect the user to the protected page
The last and final step is to rewrite the Login and Profile Page. Let's start with the Login Page.
auth
. Actually, every time a user successfully signs in, we'll use the slices to persist the account state and the tokens in the storage.If there is an account object, that means that there is an active session.
Then, we use this state to check if we have to redirect the user to the protected page
return <Route {...props} />;
or he is directly redirected to the login page return <Redirect to={"/login"} />;
.The last and final step is to rewrite the Login and Profile Page. Let's start with the Login Page.
// ./src/pages/Login.tsx
import authSlice from "../store/slices/auth";
...
const handleLogin = (email: string, password: string) => {
axios
.post(`${process.env.REACT_APP_API_URL}/auth/login/`, { email, password })
.then((res) => {
dispatch(
authSlice.actions.setAuthTokens({
token: res.data.access,
refreshToken: res.data.refresh,
})
);
dispatch(authSlice.actions.setAccount(res.data.user));
setLoading(false);
history.push("/");
})
.catch((err) => {
setMessage(err.response.data.detail.toString());
});
};
...
And the profile Page,
// ./src/pages/Profile.tsx
import authSlice from "../store/slices/auth";
...
const handleLogout = () => {
dispatch(authSlice.actions.logout());
history.push("/login");
};
...
And we're done with the front end. Start your server again and try to log in with the user-created with POSTMAN.
But there is something missing.
Our API is using refresh/access logic for authentication.
It means that when the access token expires (5 minutes), we need to get a new access token to make requests to protected resources.
It can be done in two ways:
Our API is using refresh/access logic for authentication.
It means that when the access token expires (5 minutes), we need to get a new access token to make requests to protected resources.
It can be done in two ways:
axios
and axios-auth-refresh
.
Here's how it'll work: In your terminal, install a new package:
yarn add axios-auth-refresh
Once it's done, create a new directory named
utils
, and inside this directory, create a file named axios.ts
. It will contain the code of our fetcher.import axios from 'axios';
import createAuthRefreshInterceptor from 'axios-auth-refresh';
import store from '../store';
import authSlice from '../store/slices/auth';
const axiosService = axios.create({
baseURL: process.env.REACT_APP_API_URL,
headers: {
'Content-Type': 'application/json',
},
});
axiosService.interceptors.request.use(async (config) => {
const { token } = store.getState().auth;
if (token !== null) {
config.headers.Authorization = 'Bearer ' + token;
// @ts-ignore
console.debug('[Request]', config.baseURL + config.url, JSON.stringify(token));
}
return config;
});
axiosService.interceptors.response.use(
(res) => {
// @ts-ignore
console.debug('[Response]', res.config.baseURL + res.config.url, res.status, res.data);
return Promise.resolve(res);
},
(err) => {
console.debug(
'[Response]',
err.config.baseURL + err.config.url,
err.response.status,
err.response.data
);
return Promise.reject(err);
}
);
// @ts-ignore
const refreshAuthLogic = async (failedRequest) => {
const { refreshToken } = store.getState().auth;
if (refreshToken !== null) {
return axios
.post(
'/auth/refresh/',
{
refresh: refreshToken,
},
{
baseURL: process.env.REACT_APP_API_URL
}
)
.then((resp) => {
const { access, refresh } = resp.data;
failedRequest.response.config.headers.Authorization = 'Bearer ' + access;
store.dispatch(
authSlice.actions.setAuthTokens({ token: access, refreshToken: refresh })
);
})
.catch((err) => {
if (err.response && err.response.status === 401) {
store.dispatch(authSlice.actions.setLogout());
}
});
}
};
createAuthRefreshInterceptor(axiosService, refreshAuthLogic);
export function fetcher<T = any>(url: string) {
return axiosService.get<T>(url).then((res) => res.data);
}
export default axiosService;
Then let's use this on the profile page.
import React from "react";
import {useDispatch, useSelector} from "react-redux";
import {useHistory, useLocation} from "react-router";
import authSlice from "../store/slices/auth";
import useSWR from 'swr';
import {fetcher} from "../utils/axios";
import {UserResponse} from "../utils/types";
import {RootState} from "../store";
interface LocationState {
userId: string;
}
const Profile = () => {
const account = useSelector((state: RootState) => state.auth.account);
const dispatch = useDispatch();
const history = useHistory();
const userId = account?.id;
const user = useSWR<UserResponse>(`/user/${userId}/`, fetcher)
const handleLogout = () => {
dispatch(authSlice.actions.setLogout());
history.push("/login");
};
return (
<div className="w-full h-screen">
<div className="w-full p-6">
<button
onClick={handleLogout}
className="rounded p-2 w-32 bg-red-700 text-white"
>
Deconnexion
</button>
</div>
{
user.data ?
<div className="w-full h-full text-center items-center">
<p className="self-center my-auto">Welcome, {user.data?.username}</p>
</div>
:
<p className="text-center items-center">Loading ...</p>
}
</div>
);
};
export default Profile;
The new Profile page will look like this.

That's some basic stuff if you need to build an authentication system with React and Django.
However, the application has some issues, and trying to perfect it here was only going to increase the length of the article.
So here are the issues and the solutions :
However, the application has some issues, and trying to perfect it here was only going to increase the length of the article.
So here are the issues and the solutions :
In this article, We learned to build a CRUD application web with Django and React. And as every article can be made better so your suggestion or questions are welcome in the comment section. 😉
27