DEV Community

Pedro Henrique Machado
Pedro Henrique Machado

Posted on

JWT Authentication With NodeJS

Create a Custom Authentication System with Node.js and JWT

Authentication is essential for modern web applications. In this tutorial, we’ll build a custom authentication system using Node.js, JWT, and React. You'll learn how to set up secure user registration and login, manage authentication states, and handle token expiration with refresh tokens.


Prerequisites

Before starting, ensure you have:

  • Node.js installed on your system.
  • Basic understanding of React, Node.js, and JavaScript.
  • A code editor like VS Code.

Step 1: Set Up the Backend

Start by creating the backend for handling user authentication.

1.1 Initialize the Project

mkdir node-jwt-auth cd node-jwt-auth npm init -y 
Enter fullscreen mode Exit fullscreen mode

1.2 Install Required Libraries

npm install express jsonwebtoken bcrypt cors 
Enter fullscreen mode Exit fullscreen mode

Step 2: Implement the Backend

2.1 Create the Server

Create a file named server.js in the project root:

const express = require("express"); const jwt = require("jsonwebtoken"); const bcrypt = require("bcrypt"); const cors = require("cors"); const app = express(); app.use(express.json()); // Built-in middleware to parse JSON requests app.use(cors()); // Mock user database const users = []; // JWT secret keys const ACCESS_TOKEN_SECRET = "your_access_token_secret"; const REFRESH_TOKEN_SECRET = "your_refresh_token_secret"; // Refresh token storage let refreshTokens = []; // User registration endpoint app.post("/register", async (req, res) => { const { username, password } = req.body; // Check if user already exists if (users.find((user) => user.username === username)) { return res.status(400).json({ error: "User already exists" }); } // Hash the password const hashedPassword = await bcrypt.hash(password, 10); // Save user users.push({ username, password: hashedPassword }); res.status(201).json({ message: "User registered successfully" }); }); // User login endpoint app.post("/login", async (req, res) => { const { username, password } = req.body; // Find user const user = users.find((user) => user.username === username); if (!user || !(await bcrypt.compare(password, user.password))) { return res.status(400).json({ error: "Invalid username or password" }); } // Generate tokens const accessToken = jwt.sign({ username }, ACCESS_TOKEN_SECRET, { expiresIn: "15m", }); const refreshToken = jwt.sign({ username }, REFRESH_TOKEN_SECRET); refreshTokens.push(refreshToken); // Store refresh token res.json({ accessToken, refreshToken }); }); // Refresh token endpoint app.post("/token", (req, res) => { const { token } = req.body; if (!token || !refreshTokens.includes(token)) { return res.status(403).json({ error: "Invalid refresh token" }); } try { const user = jwt.verify(token, REFRESH_TOKEN_SECRET); const accessToken = jwt.sign( { username: user.username }, ACCESS_TOKEN_SECRET, { expiresIn: "15m", } ); res.json({ accessToken }); } catch { res.status(403).json({ error: "Invalid token" }); } }); // Logout endpoint app.post("/logout", (req, res) => { const { token } = req.body; refreshTokens = refreshTokens.filter((t) => t !== token); res.status(200).json({ message: "Logged out successfully" }); }); // Protected route app.get("/protected", (req, res) => { const authHeader = req.headers.authorization; const token = authHeader && authHeader.split(" ")[1]; if (!token) return res.status(401).json({ error: "Unauthorized" }); try { const decoded = jwt.verify(token, ACCESS_TOKEN_SECRET); res.json({ message: "Welcome to the protected route!", user: decoded }); } catch { res.status(403).json({ error: "Invalid or expired token" }); } }); // Start the server app.listen(3001, () => { console.log("Server running on http://localhost:3001"); }); 
Enter fullscreen mode Exit fullscreen mode

Step 3: Set Up the Frontend

3.1 Initialize the React App

npx create-react-app client cd client npm install axios 
Enter fullscreen mode Exit fullscreen mode

Step 4: Create React Components

4.1 Create an AuthContext

Inside the src folder, create a new file AuthContext.js:

import React, { createContext, useState, useContext } from "react"; import axios from "axios"; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); const login = async (username, password) => { try { const { data } = await axios.post("http://localhost:3001/login", { username, password, }); localStorage.setItem("accessToken", data.accessToken); localStorage.setItem("refreshToken", data.refreshToken); setUser({ username }); } catch (err) { console.error(err); alert("Invalid credentials"); } }; const logout = async () => { const refreshToken = localStorage.getItem("refreshToken"); await axios.post("http://localhost:3001/logout", { token: refreshToken }); localStorage.removeItem("accessToken"); localStorage.removeItem("refreshToken"); setUser(null); }; return ( <AuthContext.Provider value={{ user, login, logout }}> {children} </AuthContext.Provider>  ); }; export const useAuth = () => useContext(AuthContext); 
Enter fullscreen mode Exit fullscreen mode

4.2 Create Login and Protected Components

Login Component
import React, { useState } from "react"; import { useAuth } from "./AuthContext"; const Login = () => { const { login } = useAuth(); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); return ( <div> <h1>Login</h1>  <input type="text" placeholder="Username" value={username} onChange={(e) => setUsername(e.target.value)} />  <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} />  <button onClick={() => login(username, password)}>Login</button>  </div>  ); }; export default Login; 
Enter fullscreen mode Exit fullscreen mode
Protected Component
import React, { useEffect, useState } from "react"; import axios from "axios"; const Protected = () => { const [message, setMessage] = useState(""); useEffect(() => { const fetchData = async () => { try { const token = localStorage.getItem("accessToken"); const { data } = await axios.get("http://localhost:3001/protected", { headers: { Authorization: `Bearer ${token}` }, }); setMessage(data.message); } catch { setMessage("Unauthorized"); } }; fetchData(); }, []); return <div>{message}</div>; }; export default Protected; 
Enter fullscreen mode Exit fullscreen mode

Step 5: Run the Application

  1. Start the backend server:
 node server.js 
Enter fullscreen mode Exit fullscreen mode
  1. Start the React frontend:
 npm start 
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

Congratulations! You've built a secure authentication system with Node.js, JWT, and React. For more tutorials, visit my YouTube Channel.

Top comments (0)