DEV Community

oyedeletemitope
oyedeletemitope

Posted on • Updated on

Login Authentication With React And FastAPI

Introduction

In this tutorial, we'll be building a login authentication using React and FastApi. This will help show how we can use both packages for a login authentication process but before that, let's take at React and also what FastApi is.

What is FastApi

FastAPI is a modern, fast (high-performance) web framework for building APIs with Python. It supports both synchronous and asynchronous actions, data validation, authentication, and interactive API documentation, all of which are powered by OpenAPI. It comes with exciting features like:

What is React

React is a user interface development library. It can construct full-stack apps by talking with a server/API and operates as a SPA (single page app) on the client. Because it is competent and directly equivalent to frameworks like Angular or Vue, React is frequently referred to as a frontend "framework".

Requirements

  • Python installed.
  • Basic knowledge of Javascript.
  • Basic Knowledge of React.
  • Knowledge on python is a plus.

Installing FastAPI

open up our terminal and cd into our project folder and write the following:

mkdir backend
Enter fullscreen mode Exit fullscreen mode

cd into the just created folder and run the following:

pip install fastapi
Enter fullscreen mode Exit fullscreen mode
pip install "uvicorn[standard]"
Enter fullscreen mode Exit fullscreen mode
pip install pyjwt
Enter fullscreen mode Exit fullscreen mode

let's leave that for later and proceed with building our frontend.

Building The Frontend

let's create and app and install the following packages:

npx create-react-app frontend
Enter fullscreen mode Exit fullscreen mode

Next we install the following packages:

npm install axios react-router-dom
Enter fullscreen mode Exit fullscreen mode

After we've done that, navigate to src/index.js and import BrowserRouter :

import { BrowserRouter } from "react-router-dom";
Enter fullscreen mode Exit fullscreen mode

We then have to replace the React.StrictMode tags with this:

<BrowserRouter>
    <App />
  </BrowserRouter>,
Enter fullscreen mode Exit fullscreen mode

Now head over to app.js and import this:

import { Routes, Route } from "react-router-dom";
import Login from "./login";
import Profile from "./Profile";
Enter fullscreen mode Exit fullscreen mode

Inside our return() lets delete our div and replace it with this:

  <div className ="App">
    <Routes><!---what are routes in react-!>
      <Route path="/" element = {<Login/>}/>
      <Route path="/profile" element = {<Profile/>}/>
    </Routes>
    </div>
Enter fullscreen mode Exit fullscreen mode

Here we are using the routes to the pages we'll be creating shortly. Next let's create a file called Login.js in our src folder and paste this:

export default function Login() {
  return (
    <>
      <h1>login page</h1>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

We'll also create another file called Profile.js and paste this:

export default function Profile() {
  return (
    <>
      <h1>profile page</h1>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now let's start our application:

npm start
Enter fullscreen mode Exit fullscreen mode

login

profile

As you can see, our page is working fine (also check the profile page by adding /profile to the url). Now that we're done with basics, let's proceed to setting up our authentication.

Let's create a new file in our src folder called Auth.js and paste this:

import { useLocation,Navigate } from "react-router-dom"

export const setToken = (token)=>{

    localStorage.setItem('temitope', token)// make up your own token
}

export const fetchToken = (token)=>{

    return localStorage.getItem('temitope')
}

export function RequireToken({children}){

    let auth = fetchToken()
    let location = useLocation()

    if(!auth){

        return <Navigate to='/' state ={{from : location}}/>;
    }

    return children;
}
}
Enter fullscreen mode Exit fullscreen mode

Here we created variables setting our token, fetching and also requiring our token, so let's go back to our app.js and import our token:

import { RequireToken } from "./Auth";
Enter fullscreen mode Exit fullscreen mode

We'll be adding some things in our app.js. In our Route path="/profile" let's make changes to the element by adding our RequireToken so our Route path="/profile" should look like this:

<Route
  path="/profile"
  element={
    <RequireToken>
      <Profile />
    </RequireToken>
  }
/>
Enter fullscreen mode Exit fullscreen mode

When we save this and go to our app, we see that our profile page is now protected and can only be accessed with a valid token. Now let's finish our login page with our login form. Head over to the login page, clear all, and paste this:

import { useNavigate } from "react-router";
import { fetchToken } from "./Auth";

export default function Login() {
  const navigate = useNavigate();
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");

  //check to see if the fields are not empty
  const login = () => {
    if ((username == "") & (password == "")) {
      return;
    } else {
      // make api call to our backend. we'll leave this for later
    }
  };

  return (
    <>
      <div style={{ minHeight: 800, marginTop: 30 }}>
        <h1>login page</h1>
        <div style={{ marginTop: 30 }}>
          {fetchToken() ? (
            <p>you are logged in</p>
          ) : (
            <div>
              <form>
                <label style={{ marginRight: 10 }}>Input Username</label>
                <input
                  type="text"
                  onChange={(e) => setUsername(e.target.value)}
                />

                <label style={{ marginRight: 10 }}>Input Password</label>
                <input
                  type="text"
                  onChange={(e) => setPassword(e.target.value)}
                />

                <button onClick={login}>Login</button>
              </form>
            </div>
          )}
        </div>
      </div>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

We'll be pausing there for now. It's time to work on our backend.

Creating the backend

Now lets open up our backend folder, create a main.py file and input the following:

from fastapi import FastAPI
from pydantic import BaseModel
import jwt
from pydantic import BaseModel
from fastapi.encoders import jsonable_encoder
from fastapi.middleware.cors import CORSMiddleware


SECERT_KEY = "YOUR_FAST_API_SECRET_KEY"
ALGORITHM ="HS256"
ACCESS_TOKEN_EXPIRES_MINUTES = 800

test_user = {
   "username": "temitope",
    "password": "temipassword",

}

app = FastAPI()

origins = {
    "http://localhost",
    "http://localhost:3000",
}

app.add_middleware(
   CORSMiddleware,
    allow_origins = origins,
    allow_credentials =True,
    allow_methods = ["*"],
    allow_headers= ["*"],
)

class LoginItem(BaseModel):
    username: str
    password: str

    @app.get("/")
    def read_root():
     return {"Hello": "World"}

@app.post("/login")
async def user_login(loginitem:LoginItem):


    data = jsonable_encoder(loginitem)

    if data['username']== test_user['username'] and data['password']== test_user['password']:

        encoded_jwt = jwt.encode(data, SECERT_KEY, algorithm=ALGORITHM)
        return {"token": encoded_jwt}

    else:
        return {"message":"login failed"}
Enter fullscreen mode Exit fullscreen mode

Here we are trying to:

  • Generate a token.
  • Defining a test user object to check against the user login credentials
  • Configuring our CORS to allow our React app to send POST requests
  • Running a check with the coming data with test_user.

Almost done now that we're done, let's go back to the frontend to finish things up. Head over to login.js and replace it with this:

import { useNavigate } from "react-router";
import { fetchToken, setToken } from "./Auth";
import { useState } from "react";
import axios from "axios";

export default function Login() {
  const navigate = useNavigate();
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");

  //check to see if the fields are not empty
  const login = () => {
    if ((username == "") & (password == "")) {
      return;
    } else {
      // make api call to our backend. we'll leave thisfor later
      axios
        .post("http://localhost:8000/login", {
          username: username,
          password: password,
        })
        .then(function (response) {
          console.log(response.data.token, "response.data.token");
          if (response.data.token) {
            setToken(response.data.token);
            navigate("/profile");
          }
        })
        .catch(function (error) {
          console.log(error, "error");
        });
    }
  };

  return (
    <div style={{ minHeight: 800, marginTop: 30 }}>
      <h1>login page</h1>
      <div style={{ marginTop: 30 }}>
        {fetchToken() ? (
          <p>you are logged in</p>
        ) : (
          <div>
            <form>
              <label style={{ marginRight: 10 }}>Input Username</label>
              <input
                type="text"
                onChange={(e) => setUsername(e.target.value)}
              />

              <label style={{ marginRight: 10 }}>Input Password</label>
              <input
                type="text"
                onChange={(e) => setPassword(e.target.value)}
              />

              <button type="button" onClick={login}>
                Login
              </button>
            </form>
          </div>
        )}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

We'll also make changes to our profile.js so let's open it up and paste this:

import { useNavigate } from "react-router";
export default function Profile() {
  const navigate = useNavigate();

  const signOut = () => {
    localStorage.removeItem("temitope");
    navigate("/");
  };

  return (
    <>
      <div style={{ marginTop: 20, minHeight: 700 }}>
        <h1>Profile page</h1>
        <p>Hello there, welcome to your profile page</p>

        <button onClick={signOut}>sign out</button>
      </div>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

We're done, let' test our app . Run the code:

uvicorn main:app --reload
Enter fullscreen mode Exit fullscreen mode

finish

Conclusion

In the tutorial, we looked at what FastApi is and also what React is. We also learned how to install FastApi and React using these ideas to build our login authentication. Here's a link to the repo on github. Happy coding!

Latest comments (5)

Collapse
 
lieranderl profile image
Evgeni

How do you verify the token you saved in local storage?! I can set any data as a token in local storage and access the profile page without authentication!

Collapse
 
philip64272375 profile image
philip

IT DOESNT WORK NONE OF IT WORKS

Collapse
 
aleksyniemir profile image
aleksyniemir

Nothing works. Spend a few hours debugging, still didn't solve all the bugs

Collapse
 
omergutman profile image
omer-gutman

I have also phased issues with this code, nothing works. Is there an updated code that works?

Collapse
 
manny22isaac profile image
manny22isaac

How about users signing up?