Open In App

Build an Authentication System Using Django, React and Tailwind

Last Updated : 08 Apr, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

In this article, we will guide you in building an Authentication system using React and Tailwind with the Django Framework that includes features like sign up, log in, forgot password, and reset password. We’ll explore the integration of Django, React, and Tailwind CSS and go through the step-by-step process of implementing the Authentication system.

What is an Authentication System?

An Authentication system is a mechanism designed to confirm the identity of users seeking access to restricted areas or features within a site. Users typically provide credentials like usernames and passwords, which are then verified against stored data. Upon successful authentication, the system establishes a session for the user, allowing them to access authorized content without repeated logins.

Authentication System Using Django, React, and Tailwind

Here, is the step-by-step implementation of the Authentication system using ReactTailwind, and Django Framework. Here, we will cover the article in 2 parts, frontend and then backend.

Backend Using Django

To start the project and app, use the following command:

django-admin startproject backend
cd backend
python manage.py startapp api

Now add this app to the 'settings.py' file:

INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"api",
"corsheaders",
"rest_framework",
]

To install the 'corsheaders' and 'rest_framework' packages run the below command:

pip install django-cors-headers djangorestframework

File Structure

backend-folder
Django backend folder structure

Setting Necessary Files

api/models.py : Below code defines two Django models: Token and User. Token includes fields for ID, token, creation and expiration dates, user ID, and token usage status. User has fields for ID, name, unique email, password, optional phone number, and country. The User model's __str__ method returns the user's name.

# api/models.py
from django.db import models

# Create your models here.


class Token(models.Model):
    id = models.AutoField(primary_key=True)
    token = models.CharField(max_length=255)
    created_at = models.DateTimeField()
    expires_at = models.DateTimeField()
    user_id = models.IntegerField()
    is_used = models.BooleanField(default=False)


class User(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255)
    email = models.EmailField(unique=True)
    password = models.CharField(max_length=255)
    phone = models.CharField(max_length=10, null=True)
    country = models.CharField(max_length=63)

    def __str__(self) -> str:
        return self.name

api/serializers.py : Below code defines two Django Rest Framework serializers: UserSerializer and TokenSerializer. Both inherit from serializers.ModelSerializer. UserSerializer specifies the model as User and includes all fields from the model in the serialization. TokenSerializer specifies the model as Token and includes all fields from the model in the serialization.

# api/serializers.py

from rest_framework import serializers
from .models import User, Token


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["name", "email", "password", "country", "phone"]


class TokenSerializer(serializers.ModelSerializer):
    class Meta:
        model = Token
        fields = ["token", "created_at", "expires_at", "user_id", "is_used"]

api/views.py : Below code defines Django views using Django Rest Framework. LoginView verifies user existence and password match using email. It returns success or error response. RegistrationView creates a new User with provided details. ForgotPasswordView generates a token for password reset, sending it to the user's email.

# api/views.py

from django.shortcuts import render
from django.contrib.auth.hashers import make_password
from django.core.mail import send_mail
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import User, Token
from .serializers import UserSerializer, TokenSerializer
from django.conf import settings
from datetime import datetime, timedelta
import hashlib
import uuid
from django.utils import timezone

SALT = "8b4f6b2cc1868d75ef79e5cfb8779c11b6a374bf0fce05b485581bf4e1e25b96c8c2855015de8449"
URL = "http://localhost:3000"


def mail_template(content, button_url, button_text):
    return f"""<!DOCTYPE html>
            <html>
            <body style="text-align: center; font-family: "Verdana", serif; color: #000;">
                <div style="max-width: 600px; margin: 10px; background-color: #fafafa; padding: 25px; border-radius: 20px;">
                <p style="text-align: left;">{content}</p>
                <a href="{button_url}" target="_blank">
                    <button style="background-color: #444394; border: 0; width: 200px; height: 30px; border-radius: 6px; color: #fff;">{button_text}</button>
                </a>
                <p style="text-align: left;">
                    If you are unable to click the above button, copy paste the below URL into your address bar
                </p>
                <a href="{button_url}" target="_blank">
                    <p style="margin: 0px; text-align: left; font-size: 10px; text-decoration: none;">{button_url}</p>
                </a>
                </div>
            </body>
            </html>"""


# Create your views here.
class ResetPasswordView(APIView):
    def post(self, request, format=None):
        user_id = request.data["id"]
        token = request.data["token"]
        password = request.data["password"]

        token_obj = Token.objects.filter(
            user_id=user_id).order_by("-created_at")[0]
        if token_obj.expires_at < timezone.now():
            return Response(
                {
                    "success": False,
                    "message": "Password Reset Link has expired!",
                },
                status=status.HTTP_200_OK,
            )
        elif token_obj is None or token != token_obj.token or token_obj.is_used:
            return Response(
                {
                    "success": False,
                    "message": "Reset Password link is invalid!",
                },
                status=status.HTTP_200_OK,
            )
        else:
            token_obj.is_used = True
            hashed_password = make_password(password=password, salt=SALT)
            ret_code = User.objects.filter(
                id=user_id).update(password=hashed_password)
            if ret_code:
                token_obj.save()
                return Response(
                    {
                        "success": True,
                        "message": "Your password reset was successfully!",
                    },
                    status=status.HTTP_200_OK,
                )


class ForgotPasswordView(APIView):
    def post(self, request, format=None):
        email = request.data["email"]
        user = User.objects.get(email=email)
        created_at = timezone.now()
        expires_at = timezone.now() + timezone.timedelta(1)
        salt = uuid.uuid4().hex
        token = hashlib.sha512(
            (str(user.id) + user.password + created_at.isoformat() + salt).encode(
                "utf-8"
            )
        ).hexdigest()
        token_obj = {
            "token": token,
            "created_at": created_at,
            "expires_at": expires_at,
            "user_id": user.id,
        }
        serializer = TokenSerializer(data=token_obj)
        if serializer.is_valid():
            serializer.save()
            subject = "Forgot Password Link"
            content = mail_template(
                "We have received a request to reset your password. Please reset your password using the link below.",
                f"{URL}/resetPassword?id={user.id}&token={token}",
                "Reset Password",
            )
            send_mail(
                subject=subject,
                message=content,
                from_email=settings.EMAIL_HOST_USER,
                recipient_list=[email],
                html_message=content,
            )
            return Response(
                {
                    "success": True,
                    "message": "A password reset link has been sent to your email.",
                },
                status=status.HTTP_200_OK,
            )
        else:
            error_msg = ""
            for key in serializer.errors:
                error_msg += serializer.errors[key][0]
            return Response(
                {
                    "success": False,
                    "message": error_msg,
                },
                status=status.HTTP_200_OK,
            )


class RegistrationView(APIView):
    def post(self, request, format=None):
        request.data["password"] = make_password(
            password=request.data["password"], salt=SALT
        )
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(
                {"success": True, "message": "You are now registered on our website!"},
                status=status.HTTP_200_OK,
            )
        else:
            error_msg = ""
            for key in serializer.errors:
                error_msg += serializer.errors[key][0]
            return Response(
                {"success": False, "message": error_msg},
                status=status.HTTP_200_OK,
            )


class LoginView(APIView):
    def post(self, request, format=None):
        email = request.data["email"]
        password = request.data["password"]
        hashed_password = make_password(password=password, salt=SALT)
        user = User.objects.get(email=email)
        if user is None or user.password != hashed_password:
            return Response(
                {
                    "success": False,
                    "message": "Invalid Login Credentials!",
                },
                status=status.HTTP_200_OK,
            )
        else:
            return Response(
                {"success": True, "message": "You are now logged in!"},
                status=status.HTTP_200_OK,
            )

api/urls.py : Here, Django code configures a multiple URL routes, linking the 'register/', 'login/', 'forgotPassword/' and 'resetPassword/' linking to their view functions in views.py file.

# api/urls.py

from django.urls import path
from .views import RegistrationView, LoginView, ForgotPasswordView, ResetPasswordView

urlpatterns = [
    path("register", RegistrationView.as_view(), name="register"),
    path("login", LoginView.as_view(), name="login"),
    path("forgotPassword", ForgotPasswordView.as_view(), name="forgotPassword"),
    path("resetPassword", ResetPasswordView.as_view(), name="resetPassword"),
]

backend/urls.py : Here, Django code defines URL patterns, routing requests to the Django admin interface at '/admin/' and including additional patterns from the 'api.urls'.

# backend/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls')),
]

backend/settings.py : In settings.py we added the crosheaders Middleware and also the some allowing host for integrating React. We also add the EMAIL related credentials to be used in our views.py file.

# backend/settings.py

MIDDLEWARE = [
    ............................... ......................... ...............
    ............................ ......................... .....................
    'corsheaders.middleware.CorsMiddleware',
]

CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
]

CORS_ALLOW_CREDENTIALS = True

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.gmail.com"
EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST_USER = "Your_Email"
EMAIL_HOST_PASSWORD = "Your_Password"
TIME_ZONE = 'Asia/Kolkata'

api/admin.py : In admin.py file, we will register our models by copying the following code. This code imports the models and then calls admin.site.register to register each of them.

from django.contrib import admin
from .models import User, Token

# Register your models here.
admin.site.register(User)
admin.site.register(Token)

Apply Migrations in Project

Now, apply we will the migration changes which will create a `db.sqlite3` file that will contain our database.

python manage.py makemigrations
python manage.py migrate

Create a superuser using the below command:

python3 manage.py createsuperuser

Frontend Using React and Tailwind CSS

To start the project in react use the below command:

npx create-react-app frontend
cd frontend

Install the necessary libraries like tailwindcss, axios, etc. using the below command:

npm install tailwindcss axios @tailwindcss/forms flowbite flowbite-react react-toastify react-router-dom

File Structure

frontend-folder
React frontend folder structure

Creating GUI

src/App.js : The App component in this React code manages an Authentication system application. Its UI includes pages for login (Login), register (Register), forgot password (ForgotPassword), and reset password (ResetPassword). Browser routing is implemented to render different components based on different route URLs.

// App.js

import { BrowserRouter, Route, Routes } from "react-router-dom";
import "./App.css";
import AppNavBar from "./components/AppNavBar";
import Login from "./pages/Login";
import Register from "./pages/Register";
import Home from "./pages/Home";
import ForgotPassword from "./pages/ForgotPassword";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import ResetPassword from "./pages/ResetPassword";
import Profile from "./pages/Profile";
import { useState } from "react";

const App = () => {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  return (
    <div className="md:h-screen bg-purple-100">
      <BrowserRouter>
        <ToastContainer />
        <AppNavBar
          isLoggedIn={isLoggedIn}
          setIsLoggedIn={setIsLoggedIn}
          name={name}
          setName={setName}
          email={email}
          setEmail={setEmail}
        />
        <div>
          <Routes>
            <Route path="/" exact
              element={
                <Home isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} />
              }
            />
            <Route path="register" exact
              element={
                <Register
                  isLoggedIn={isLoggedIn}
                  setIsLoggedIn={setIsLoggedIn}
                  setName={setName}
                  setEmail={setEmail}
                />
              }
            />
            <Route path="login" exact
              element={
                <Login
                  isLoggedIn={isLoggedIn}
                  setIsLoggedIn={setIsLoggedIn}
                  setName={setName}
                  setEmail={setEmail}
                />
              }
            />
            <Route path="forgotPassword" exact
              element={<ForgotPassword isLoggedIn={isLoggedIn} />}
            />
            <Route path="resetPassword" 
              element={<ResetPassword isLoggedIn={isLoggedIn} />}
            />
            <Route path="profile" exact
              element={
                <Profile isLoggedIn={isLoggedIn} name={name} email={email} />
              }
            />
          </Routes>
        </div>
      </BrowserRouter>
    </div>
  );
};

export default App;

src/index.css : These `@import` statements bring in the base, components, and utilities stylesheets of Tailwind CSS, enabling the use of its comprehensive utility classes in a project’s styling.

frontend/tailwind.config.js: The configuration file for Tailwind CSS. Here we are using Flowbite's plugin for TailwindCSS to develop our UI easily with awesome components already provided.

// tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
    "node_modules/flowbite-react/lib/esm/**/*.js",
    "./node_modules/flowbite/**/*.js"
  ],
  theme: {
    extend: {},
  },
  plugins: [require("@tailwindcss/forms"), require("flowbite/plugin")],
};

components/AppNavbar.js: This component is to render navbar on every page. It displays the Login link based on whether user is logged in identified from `isLoggedIn` variable passed as prop.

// components/AppNavbar.js

import { Avatar, Dropdown, Navbar } from "flowbite-react";
import UserIcon from "../images/user.png";
import { toast } from "react-toastify";
import { useNavigate } from "react-router-dom";

const AppNavBar = (props) => {
  let navigate = useNavigate();

  const { isLoggedIn, setIsLoggedIn, name, setName, email, setEmail } = props;

  const handleLogout = () => {
    setIsLoggedIn(false);
    setName(null);
    setEmail(null);
    navigate("/");
    toast.success("You are successfully logged out!");
  };

  return (
    <Navbar fluid>
      <Navbar.Brand href="https://girishgr8.github.io">
        <img
          src="https://media.geeksforgeeks.org/wp-content/uploads/20210224040124/JSBinCollaborativeJavaScriptDebugging6-300x160.png"
          className="mr-3 h-6 sm:h-9"
          alt="Flowbite React Logo"
        />
        <span className="self-center whitespace-nowrap text-3xl font-semibold dark:text-white">GeeksForGeeks</span>
      </Navbar.Brand>
      {isLoggedIn && (
        <div className="flex md:order-2">
          <Dropdown arrowIcon={false} inline
            label={<Avatar alt="User settings" img={UserIcon} rounded />}>
            <Dropdown.Header>
              <span className="block text-sm">{name}</span>
              <span className="block truncate text-sm font-medium">{email}</span>
            </Dropdown.Header>
            <Dropdown.Item>Settings</Dropdown.Item>
            <Dropdown.Item>Your Orders</Dropdown.Item>
            <Dropdown.Divider />
            <Dropdown.Item onClick={handleLogout}>Log out</Dropdown.Item>
          </Dropdown>
          <Navbar.Toggle />
        </div>
      )}
      <Navbar.Collapse>
        <Navbar.Link href="/" className="text-lg">Home</Navbar.Link>
        <Navbar.Link href="#" className="text-lg">About</Navbar.Link>
        <Navbar.Link href="#" className="text-lg">Services</Navbar.Link>
        <Navbar.Link href="#" className="text-lg">Pricing</Navbar.Link>
        <Navbar.Link href="#" className="text-lg">Contact</Navbar.Link>
        {!isLoggedIn && (
          <Navbar.Link href="/login" className="text-lg">Login</Navbar.Link>
        )}
      </Navbar.Collapse>
    </Navbar>
  );
};

export default AppNavBar;

components/CountryInput.js: This component is used when user registers on website, where the user has to provide his country. It is just like a dropdown component to select a particular value from list of values here countries.

// component/CountryInput.js

const countries = [
  "Select Country",
  "India",
  "United States",
  "Japan",
  "Australia",
  "Canada",
];

const CountryInput = () => {
  return (
    <div className="max-w-xl">
      <div className="mb-2 block">
        <label htmlFor="country" className="text-sm font-medium required">
          Country
        </label>
      </div>
      <select
        id="country"
        name="country"
        class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-purple-500 focus:border-purple-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-purple-500 dark:focus:border-purple-500"
        required
      >
        {countries.map((el, index) => (
          <option key={index}>{el}</option>
        ))}
      </select>
    </div>
  );
};

export default CountryInput;

frontend /.env : This file stores our secret variables that should not be exposed or revealed. We store our backend URL route in this file.

REACT_APP_BACKEND_URL=http://localhost:8000

Pages/file_name

Now, we have different pages develop UI and logic for pages such as Home, Login , Register, Profile, ForgotPassword and ResetPassword pages. To make HTTP API requests to our Django backend we use axios library which we installed earlier. Also, useState() and useNavigate() hooks are used while implementing the logic.

JavaScript
// pages/Home.js

const Home = () => {
  return (
    <div>
      <h2 className="font-bold my-5 text-xl text-center">This is simple home page. Customize as per your usecase</h2>
    </div>
  );
};

export default Home;
JavaScript JavaScript JavaScript JavaScript JavaScript

Deployment of the Project:

Run the backend and frontend with the help of following command:

python3 manage.py runserver
npm start

You can access the Django's admin dashboard at the following URL : http://localhost:8000/admin/

Video Demonstration


Next Article
Article Tags :
Practice Tags :

Similar Reads