React  

Implementing Login with Authentication and Authorization in React (JWT + Axios)

Modern web applications require secure authentication and controlled access to resources. In this article, we implement a login system in React that communicates with a backend API, stores JWT tokens, and enables authenticated API calls.

The architecture we implement follows industry best practices by separating UI, logic, and API layers.

1. Understanding the Authentication Flow

Before implementation, it is important to understand the complete login flow.

User enters username and password
        ↓
React calls Login API
        ↓
Server validates credentials
        ↓
Server returns Access Token + Refresh Token
        ↓
React stores tokens in localStorage
        ↓
User navigates to protected pages
        ↓
Axios automatically attaches token to API requests

This approach ensures:

  • Secure communication with backend APIs

  • Controlled access to protected resources

  • Stateless authentication using JWT tokens

2. Project Structure

To keep the application maintainable, we organize the project as follows:

src
 ├── api
 │     axiosClient.js
 │
 ├── services
 │     authService.js
 │
 ├── components
 │     LoginHeader.jsx
 │     LoginForm.jsx
 │
 ├── pages
 │     LoginPage.jsx
 │     EmployeePage.jsx
 │
 └── App.js

Each folder has a specific responsibility.

FolderResponsibility
apiAxios configuration
servicesAPI calls
componentsUI elements
pagesPage level logic
App.jsRouting

3. Creating Axios Client

The Axios client centralizes API configuration.

src/api/axiosClient.js

import axios from "axios";

const axiosClient = axios.create({
  baseURL: "http://localhost:5288/api",
  headers: {
    "Content-Type": "application/json"
  }
});

export default axiosClient;

Benefits of this approach:

  • Single place to manage API configuration

  • Easy to add interceptors

  • Reusable across the entire application

4. Creating the Authentication Service

The service layer is responsible for communicating with the backend API.

src/services/authService.js

import axiosClient from "../api/axiosClient";

export const login = async (username, password) => {

  const response = await axiosClient.post(
    `/auth/login?username=${username}&password=${password}`
  );

  return response.data;
};

This method sends credentials to the backend and returns:

{
 accessToken: "...",
 refreshToken: "..."
}

5. Creating the Login Header Component

src/components/LoginHeader.jsx

import React from 'react'

function LoginHeader() {
  return (
    <div className="bg-gray-500 text-white p-6 rounded shadow text-3xl text-center mb-6">
      Login
    </div>
  )
}

export default LoginHeader

What is this?

This component is purely responsible for UI rendering.

6. Creating the Login Form Component

The LoginForm component receives props from the parent page.

src/components/LoginForm.jsx

import React from 'react'

function LoginForm({
  userName,
  password,
  setUserName,
  setPassword,
  handleLogin
}) {

  return (

    <div>

      <input
        type="text"
        value={userName}
        placeholder="Enter User Name"
        onChange={(e)=>setUserName(e.target.value)}
      />

      <input
        type="password"
        value={password}
        placeholder="Enter Password"
        onChange={(e)=>setPassword(e.target.value)}
      />

      <br/>

      <button onClick={handleLogin}>
        Login
      </button>

    </div>

  )
}

export default LoginForm

What is this?

Key concept here:

The component does not contain business logic.
It simply receives props from the parent component.

7. Implementing the Login Page

The LoginPage manages the application logic.

src/pages/LoginPage.jsx

import React, { useState } from 'react'
import LoginForm from '../components/LoginForm'
import LoginHeader from '../components/LoginHeader'
import { login } from '../services/authService'
import { useNavigate } from "react-router-dom"

function LoginPage() {

  const navigate = useNavigate()

  const [userName, setUserName] = useState("")
  const [password, setPassword] = useState("")

  const handleLogin = async () => {

    try {

      const data = await login(userName, password)

      localStorage.setItem("accessToken", data.accessToken)
      localStorage.setItem("refreshToken", data.refreshToken)

      navigate("/employees")

    } catch (error) {

      console.error("Login failed", error)

    }
  }

  return (

    <div className="min-h-screen flex justify-center items-center bg-gray-100">

      <div className="w-full max-w-md">

        <LoginHeader />

        <LoginForm
          userName={userName}
          password={password}
          setUserName={setUserName}
          setPassword={setPassword}
          handleLogin={handleLogin}
        />

      </div>

    </div>

  )
}

export default LoginPage

Responsibilities of this page:

  • Manage state

  • Call authentication API

  • Store tokens

  • Navigate after login

8. Configuring Application Routing

src/App.js

import { BrowserRouter, Routes, Route } from "react-router-dom";

import LoginPage from "./pages/LoginPage";
import EmployeePage from "./pages/EmployeePage";

function App() {

  return (

    <BrowserRouter>

      <Routes>

        <Route path="/" element={<LoginPage />} />

        <Route path="/employees" element={<EmployeePage />} />

      </Routes>

    </BrowserRouter>

  );
}

export default App;

After successful login, the user is redirected to:

/employees

9. Storing Tokens

The access token is stored using:

localStorage.setItem("accessToken", token)

This allows future API requests to include authentication.

10. Next Step: Axios Interceptor (Important)

In real applications we configure Axios interceptors to attach the token automatically.

Example:

axiosClient.interceptors.request.use((config)=>{

  const token = localStorage.getItem("accessToken")

  if(token){
    config.headers.Authorization = `Bearer ${token}`
  }

  return config
})

This ensures every API request includes:

Authorization: Bearer <token>

11. Benefits of This Architecture

This structure provides:

  • Clean separation of concerns

  • Reusable components

  • Centralized API management

  • Scalable authentication structure

  • Industry-level React architecture

Conclusion

Implementing authentication in React requires a clear separation between:

  • UI components

  • Business logic

  • API communication

By using JWT authentication, Axios services, and proper project structure, we can build a secure and scalable authentication system suitable for real-world applications.