How to Implement a Secure JWT Authentication and Registration with React and Node.js
Karan Rana
Full Stack Developer | EX-SDE-Intern @Traxsmart | 800+ DSA @ Leetcode +GFG+Codechef | Full Stack Developer (Java+ Springboot +Microservices+Rest API+ Hibernate + HTML+CSS+JS+React Js+Node Js +Express Js) | CS Grad'23
Setting up secure user authentication can be a daunting task. But with the use of JSON Web Tokens (JWT) combined with React and Node.js, this process becomes more manageable and secure. Let’s break down how to do this.
Prerequisites
1. Installation:
Windows:
MacOS (using Homebrew):
brew tap mongodb/brew brew install [email protected]
2. Starting MongoDB:
Windows:
mongod.exe --dbpath "C:\path\to\your\data\db"
3. Accessing MongoDB:
mongo
4. Stopping MongoDB:
Windows:
MacOS (using Homebrew):
brew services stop mongodb/brew/mongodb-community
1. Backend Setup:
First, let’s get our backend up and running.
Dependencies:
mkdir jwt-backend
cd jwt-backend
npm init -y
npm install express mongoose bcrypt jsonwebtoken cors
User Schema (backend/models/user.js):
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
trim: true,
},
password: {
type: String,
required: true,
},
tokens: [
{
token: {
type: String,
required: true,
},
},
],
});
userSchema.methods.verifyPassword = async function (password) {
const user = this;
const isMatch = await bcrypt.compare(password, user.password);
return isMatch;
};
const User = mongoose.model("User", userSchema);
module.exports = User;
Setting up JWT (backend/utils/jwtHelper.js):
const jwt = require('jsonwebtoken');
const SECRET_KEY = "YOUR_SECRET_KEY"; // Store this securely!
const generateToken = (user) => {
return jwt.sign({ id: user._id, email: user.email }, SECRET_KEY, {
expiresIn: '1h'
});
};
const verifyToken = (token) => {
return jwt.verify(token, SECRET_KEY);
};
module.exports = { generateToken, verifyToken };
Setting up Routes (backend/routes/auth.js):
const express = require("express");
const jwt = require("jsonwebtoken");
const User = require("../models/user");
const router = express.Router();
const bcrypt = require("bcrypt");
router.post("/login", async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user) return res.status(400).send("Invalid username or password.");
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword)
return res.status(400).send("Invalid username or password.");
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
res.send({ token });
});
router.post("/register", async (req, res) => {
try {
const { username, password } = req.body;
const existingUser = await User.findOne({ username });
if (existingUser) {
return res.status(400).json({ error: "Username already exists." });
}
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
const user = new User({
username,
password: hashedPassword,
});
const savedUser = await user.save();
res.json({
message: "User registered successfully",
userId: savedUser._id,
});
} catch (error) {
console.error(error);
res.status(500).json({ error: "Internal server error" });
}
});
module.exports = router;
Server Configuration (backend/server.js):
const express = require("express");
const mongoose = require("mongoose");
const authRoutes = require("./routes/auth");
const cors = require("cors"); // Import the CORS middleware
require("dotenv").config();
const app = express();
const PORT = 3001;
mongoose
.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log("Connected to MongoDB");
})
.catch((err) => {
console.error("Error connecting to MongoDB", err);
});
app.use(cors()); // Use CORS middleware to allow requests from the frontend
app.use(express.json());
app.use("/api/auth", authRoutes); // All the routes defined in auth.js will be prefixed with /api/auth
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Directory Structure:
backend/
|-- models/
| |-- user.js
|-- utils/
| |-- jwthelper.js
|-- routes/
| |-- auth.js
|-- server.js
|-- package.json
2. Frontend with React:
Setting Up React:
npx create-react-app frontend
cd jwt-frontend
npm install axios
Directory Structure:
frontend/
|-- src/
| |-- components/
| | |-- LoginForm.js
| | |-- RegistrationForm.js
| | |-- AuthContext.js
| | |-- Dashboard.js
| |-- App.js
|-- package.json
LoginForm Component (frontend/components/Login.js):
import React, { useState, useContext } from "react";
import axios from "axios";
import { AuthContext } from "./AuthContext";
import { useNavigate } from "react-router-dom";
const Login = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [errorMessage, setErrorMessage] = useState(null); // New state for handling error messages
const { setToken } = useContext(AuthContext);
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post("/api/auth/login", {
username,
password,
});
setToken(response.data.token);
localStorage.setItem("token", response.data.token);
navigate("/dashboard");
} catch (error) {
console.error("Authentication failed:", error);
setToken(null);
localStorage.removeItem("token");
if (error.response && error.response.data) {
setErrorMessage(error.response.data); // Set the error message if present in the error response
} else {
setErrorMessage("An unexpected error occurred. Please try again.");
}
}
};
return (
<div>
{errorMessage && <div style={{ color: "red" }}>{errorMessage}</div>}{" "}
<form onSubmit={handleSubmit}>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Username"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Login</button>
</form>
</div>
);
};
export default Login;
Registration Component (frontend/components/Registration.js):
import React, { useState } from "react";
import axios from "axios";
const Registration = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [message, setMessage] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post("/api/auth/register", {
username,
password,
});
setMessage(response.data.message);
} catch (error) {
console.error("Registration failed:", error.response.data.error);
setMessage(error.response.data.error);
}
};
return (
<div>
<h2>Register</h2>
<form onSubmit={handleSubmit}>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Username"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
<button type="submit">Register</button>
</form>
{message && <p>{message}</p>}
</div>
);
};
export default Registration;
领英推荐
Authentication Context Component (frontend/components/AuthContext.js):
import React, { createContext, useState, useEffect } from "react";
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [token, setToken] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const storedToken = localStorage.getItem("token");
setToken(storedToken);
setLoading(false);
}, []);
return (
<AuthContext.Provider value={{ token, setToken, loading }}>
{children}
</AuthContext.Provider>
);
};
Dashboard Protected Component (frontend/components/Dashboard.js):
import { useContext } from "react";
import { AuthContext } from "./AuthContext";
import { Navigate } from "react-router-dom";
function Dashboard() {
const { token, loading } = useContext(AuthContext);
if (loading) {
return null;
}
if (!token) {
return <Navigate to="/login" replace />;
}
return <h1>Dashboard: Protected Content Here</h1>;
}
export default Dashboard;
3. Frontend Authentication Logic:
Authentication in the frontend typically revolves around managing a user’s session using tokens and controlling access to certain routes or views based on authentication status.
1. AuthContext.js:
This file defines the authentication context for your application.
App/
|-- frontend/
| |-- src/
| | |-- components/
| | | |-- LoginForm.js
| | | |-- RegistrationForm.js
| | | |-- AuthContext.js
| | | |-- Dashboard.js
| | |-- App.js
| |-- package.json
|-- backend/
| |-- models/
| | |-- user.js
| |-- utils/
| | |-- jwthelper.js
| |-- routes/
| | |-- auth.js
| |-- server.js
| |-- package.json
How to run the App?
1. Running MongoDB Locally:
Ensure MongoDB is running on your local machine. By default, MongoDB runs on localhost:27017. If you have MongoDB installed as a service, it may start automatically. If not, you can typically start it with:
mongod
2. Backend Setup:
a. Navigate to your backend directory:
cd App/backend
b. Install the required packages if you haven’t done so:
npm install
c. Ensure your server.js (or equivalent entry point) is set up to connect to your local MongoDB. Look for a connection string similar to this:
mongoose.connect('mongodb://localhost:27017/yourdbname', { useNewUrlParser: true, useUnifiedTopology: true });
Replace 'yourdbname' with the name of your database.
d. Run the backend:
npm start
3. Frontend Setup:
a. Navigate to your frontend directory:
cd App/frontend
b. Install required packages if you haven’t:
npm install
c. Set up a proxy to your backend in the frontend’s package.json. This will make the React development server proxy any unknown requests to your backend server. Add the following line:
"proxy": "https://localhost:3001",
Remember to replace 3001 with the port your backend is running on if it's different.
d. Run the frontend:
npm start
By default, React apps start on localhost:3000.
4. Access the App:
You can now access your frontend app by going to:
https://localhost:3000/
With this setup, any requests from your frontend that aren’t recognized as static assets (like your JavaScript or CSS) will be forwarded to your backend server. This is particularly useful during development to avoid CORS issues.
Follow me for such technical contents .
#JWTAuthentication #ReactNodeAuthentication #SecureAuthentication #ReactDevelopment #NodeJSDevelopment#BackendDevelopment
#FrontendDevelopment#WebDevelopment#MongoDB#AuthenticationTutorial