JWT Authentication in React + Go: A Step-by-Step Guide
Implementing JWT Authentication in a React Application with a Go Backend: A Step-by-Step Guide
Introduction
In today's web development landscape, implementing user authentication is a fundamental aspect of building secure and user-friendly applications. In this guide, we'll walk through the process of setting up user authentication in a React application, covering everything from installation to handling login, registration, and logout functionalities.
This repository contains the code for a JWT authentication project with frontend and backend components.
- Frontend (React): Frontend Repository
- Backend (Go): Backend Repository
Full project: GitHub Repository
Prerequisites:
Before we dive into the implementation, make sure you have the following:
Node.js Installed: Ensure you have Node.js installed on your system to run the React application.
Go Backend: The backend for this application is built in Go. You can follow the steps outlined in this guide:
Step 1: Installing React
To create a React application, open your terminal and run the following command:
npx create-react-app my-react-auth-app
Replace my-react-auth-app
with your preferred application name. This command sets up a new React project with the TypeScript template included.
Step 2: Starting the Application
Navigate to the project directory:
cd my-react-auth-app
npm install bootstrap
npm install react-router-dom
Start the development server:
npm start
This command runs the React application on localhost:3000
.
Step 3: Setting Up Bootstrap
Add the following link to the <head>
section of your public/index.html
file and update the title
to the desired title of your app:
<title>User Authentication App</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/>
Step 4: Adding Bootstrap Template for Sign-In Form
Choose a suitable Bootstrap template from Bootstrap examples. For this guide, we'll use the sign-in form.
Right-click on the page source and copy its HTML structure.
Copy the HTML structure into a new file
components/SignIn.js
.Convert the HTML to JSX syntax:
Replace
class
withclassName
.Close self-closing tags (e.g.,
<input>
) by adding a/
before>
.
Create
components/sign-in.css
from sign-in.css and import it toSignIn.js
.
After pasting the HTML structure into SignIn.js
and converting it to JSX, the component will look like this:
// src/components/SignIn.js
import React from 'react';
import "./sign-in.css";
const SignIn = ({ onSubmit, responseMessage, setResponseMessage }) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleEmailChange = (e) => {
setEmail(e.target.value);
};
const handlePasswordChange = (e) => {
setPassword(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
const userData = {
email: email,
password: password,
};
onSubmit(userData); // Pass user data to onSubmit function
};
return (
<main className="form-signin w-100 m-auto">
<form>
<h1 className="h3 mb-3 fw-normal">Please sign in</h1>
<div className="form-floating">
<input type="email" className="form-control" id="floatingInput" placeholder="name@example.com" value={email} onChange={handleEmailChange}/>
<label htmlFor="floatingInput">Email address</label>
</div>
<div className="form-floating">
<input type="password" className="form-control" id="floatingPassword" placeholder="Password" value={password} onChange={handlePasswordChange} required/>
<label htmlFor="floatingPassword">Password</label>
</div>
<button className="btn btn-primary w-100 py-2" type="submit">
Sign in
</button>
{responseMessage && <p className="mt-5 mb-3 text-body-secondary">{responseMessage}</p>}
</form>
</main>
);
}
export default SignIn;
Step 5: Adding Bootstrap Template for Sign-Up Form
You can replicate SignIn.js
and convert it into SignUp.js
The SignUp.js
component will look like this:
import React, { useState } from "react";
import "./sign-in.css";
const SignUp = ({ onSubmit, responseMessage, setResponseMessage }) => {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const handleNameChange = (e) => {
setName(e.target.value);
};
const handleEmailChange = (e) => {
setEmail(e.target.value);
};
const handlePasswordChange = (e) => {
setPassword(e.target.value);
};
const handleConfirmPasswordChange = (e) => {
setConfirmPassword(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
if (password !== confirmPassword) {
alert("Password and Confirm Password do not match");
return;
}
if (!isValidEmail(email)) {
alert("Invalid Email Address");
return;
}
// Send data to register page
const userData = {
name: name,
email: email,
password: password,
};
onSubmit(userData); // Pass user data to onSubmit function
};
const isValidEmail = (email) => {
// Email validation logic here (e.g., regex)
// Return true if valid, false otherwise
return /\S+@\S+\.\S+/.test(email);
};
return (
<main className="form-signin w-100 m-auto">
<form onSubmit={handleSubmit}>
<h1 className="h3 mb-3 fw-normal">Please Register</h1>
<div className="form-floating">
<input type="text" className="form-control" id="name" placeholder="Your Name" value={name} onChange={handleNameChange} required />
<label htmlFor="name">Name</label>
</div>
<div className="form-floating">
<input type="email" className="form-control" id="email" placeholder="name@example.com" value={email} onChange={handleEmailChange} required />
<label htmlFor="email">Email address</label>
</div>
<div className="form-floating">
<input type="password" className="form-control" id="password" placeholder="Password" value={password} onChange={handlePasswordChange} required />
<label htmlFor="password">Password</label>
</div>
<div className="form-floating">
<input type="password" className="form-control" id="confirmPassword" placeholder="Confirm Password" value={confirmPassword} onChange={handleConfirmPasswordChange} required />
<label htmlFor="confirmPassword">Confirm Password</label>
</div>
<button className="btn btn-primary w-100 py-2" type="submit">
Register
</button>
{responseMessage && <p className="mt-5 mb-3 text-body-secondary">{responseMessage}</p>}
</form>
</main>
);
};
export default SignUp;
Step 6: Creating Navigation Menu
Select a navigation menu template from Bootstrap examples. For this guide, we'll use a simple navbar from Bootstrap navbar-fixed.
Copy the HTML structure of the navbar.
Paste the HTML structure into a new file
components/Navbar.js
.Convert the HTML to JSX syntax, similar to the process done for the sign-in form.
Create
components/navbar-fixed.css
from navbar-fixed.css and import it toNavbar.js
.
After pasting the HTML structure into Navbar.js
and converting it to JSX, the component will look like this:
// src/components/Navbar.js
import React from 'react';
import "./navbar-fixed.css";
function Navbar() {
return (
<nav className="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<div className="container-fluid">
<a className="navbar-brand" href="#">Home</a>
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div>
<ul className="navbar-nav me-auto mb-2 mb-md-0">
<li className="nav-item">
<a className="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li className="nav-item">
<a className="nav-link" href="#">Login</a>
</li>
<li className="nav-item">
<a className="nav-link" href="#">Register</a>
</li>
</ul>
</div>
</div>
</nav>
);
}
export default Navbar;
Step 7: Creating Pages for Routing
Inside the src
directory, create a new directory called pages
. This directory will contain our different page components.
Home Page (
HomePage.js
)
// src/pages/HomePage.js
import React from 'react';
const HomePage = () => {
return (
<div>
<h2>Home Page</h2>
{/* Add your home page content here */}
</div>
);
}
export default HomePage;
Login Page (
LoginPage.js
)We will import our component
SignIn.js
in the login Page
// src/pages/LoginPage.js
import React from 'react';
import SignIn from "../components/SignIn";
const LoginPage = () => {
const handleSubmit = (userData) => {
console.log("Submitting user data:", userData);
// Add logic to send user data to login page
};
return (
<div>
<SignIn onSubmit={handleSubmit} />
</div>
);
}
export default LoginPage;
Registration Page (
RegisterPage.js
)We will import our component
SignUp.js
in the Registration Page
// RegisterPage.js
import React from 'react';
import SignUp from "../components/SignUp";
const RegisterPage = () => {
const handleSubmit = (userData) => {
console.log("Submitting user data:", userData);
// Add logic to send user data to register page
};
return (
<div>
<SignUp onSubmit={handleSubmit} />
</div>
);
}
export default RegisterPage;
Step 8: Setting Up Routes
- First let us add routes in our
Navbar.js
- Now, let's set up routes for our different pages in the
App.js
file:
// src/App.js
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import HomePage from "./pages/HomePage";
import LoginPage from "./pages/LoginPage";
import RegisterPage from "./pages/RegisterPage";
import Navbar from "./components/Navbar";
function App() {
return (
<div>
<Navbar />
<Router>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
</Routes>
</Router>
</div>
);
}
export default App;
So far below is the look of our react app
Step 9: Handling User Registration
Set Up State Variables: Set up state variables for
userName
,email
,password
, andresponseMessage
using theuseState
hook.Handle Form Submission: Create a
handleSubmit
function to handle form submission. This function sets the user input data into state variables and calls theregister
function.Define Register Function: Define an
async
function namedregister
. Inside this function, perform afetch
request to the registration endpoint (http://localhost:8000/api/register
). Handle the response accordingly. If the registration is successful, set the response message to the success message. If there's an error, set the response message to the error message.
// src/pages/RegisterPage.js
import React, { useState } from "react";
import { Navigate } from "react-router-dom";
import SignUp from "../components/SignUp";
const RegisterPage = () => {
const [userName, setUserName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [responseMessage, setResponseMessage] = useState("");
const [redirect, setRedirect] = useState(false);
const handleSubmit = (userData) => {
console.log("Submitting user data:", userData);
setUserName(userData.name);
setEmail(userData.email);
setPassword(userData.password);
register();
};
const register = async () => {
try {
const response = await fetch(`http://localhost:8000/api/register`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: userName,
email: email,
password: password,
}),
});
const responseData = await response.json();
if (response.ok) {
console.log("Registration successful");
setResponseMessage(responseData.message);
setRedirect(true); // Set redirect state to true upon successful registration
} else {
console.error("Registration failed");
setResponseMessage(responseData.error || "Registration failed");
}
} catch (error) {
console.error("Error registering user:", error);
setResponseMessage("Error registering user");
}
};
if (redirect) {
return <Navigate to="/login" />;
}
return (
<div>
<SignUp
onSubmit={handleSubmit}
responseMessage={responseMessage}
setResponseMessage={setResponseMessage}
/>
</div>
);
};
export default RegisterPage;
Step 10: Handling User Login
Set Up State Variables: Use the
useState
hook to set up state variables foremail
,password
, andresponseMessage
.Handle Form Submission: Define a
handleSubmit
function to handle form submission. This function sets the user input data into state variables and calls thelogin
function.Define Login Function: Create an asynchronous
login
function to handle the login process. Inside this function, perform afetch
request to the login endpoint (http://localhost:8000/api/login
). Handle the response accordingly. If the login is successful, set the response message to the success message. If there's an error, set the response message to the error message.
// src/pages/LoginPage.js
// src/pages/LoginPage.js
import React, { useState } from "react";
import SignIn from "../components/SignIn";
import { Navigate } from "react-router-dom";
const LoginPage = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [responseMessage, setResponseMessage] = useState("");
const [redirect, setRedirect] = useState(false);
const handleSubmit = (userData) => {
console.log("Submitting user data:", userData);
setEmail(userData.email);
setPassword(userData.password);
login();
};
const login = async () => {
try {
const response = await fetch(`http://localhost:8000/api/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
body: JSON.stringify({
email: email,
password: password,
}),
});
const responseData = await response.json();
if (response.ok) {
console.log("Login successful");
setResponseMessage(responseData.message);
setRedirect(true); // Set redirect state to true upon successful registration
} else {
console.error("Login failed");
setResponseMessage(responseData.error || "Login failed");
}
} catch (error) {
console.error("Error Authenticating user:", error);
setResponseMessage("Error Authenticating user");
}
};
if (redirect) {
return <Navigate to="/" />;
}
return (
<div>
<SignIn
onSubmit={handleSubmit}
responseMessage={responseMessage}
setResponseMessage={setResponseMessage}
/>
</div>
);
};
export default LoginPage;
If you're seeing cookies in the "Application" tab of your browser's developer tools after successful login, it indicates that your server is setting cookies upon successful authentication.
Step 11: Getting User on Home Page
State Initialization:
Initialize state variables using the
useState
hook:userName
: For storing the user's name.
UseEffect Hook:
Utilize the
useEffect
hook to perform side effects.Inside the effect, define an asynchronous function
fetchUserData
.Make a GET request to
http://localhost:8000/api/user
to fetch user data.If the response is successful (HTTP status 200), extract the user's name from the response data and update the
userName
state.Handle errors that occur during the fetch operation.
Conditional Rendering:
- If
userName
is falsy (meaning the user is not logged in), redirect the user to the login page using<Navigate to="/login" />
.
- If
Return JSX:
Return JSX containing:
- A welcome message displaying the user's name (
userName
).
- A welcome message displaying the user's name (
// App.js
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import HomePage from "./pages/HomePage";
import LoginPage from "./pages/LoginPage";
import RegisterPage from "./pages/RegisterPage";
import Navbar from "./components/Navbar";
function App() {
const [userName, setUserName] = useState("");
useEffect(() => {
const fetchUserData = async () => {
try {
const response = await fetch(`http://localhost:8000/api/user`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
});
const responseData = await response.json();
if (response.ok) {
console.log(
"Received User Data",
responseData,
"name",
responseData.name
);
setUserName(responseData.name);
} else {
console.error("User Data not received");
}
} catch (error) {
console.error("Error fetching user data:", error);
}
};
fetchUserData();
}, []);
return (
<div>
<Navbar userName={userName} />
<Router>
<Routes>
<Route path="/" element={<HomePage userName={userName} />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
</Routes>
</Router>
</div>
);
}
export default App;
Render user on Home Page
import React from "react";
const HomePage = ({ userName }) => {
console.log("Home Page userName", userName);
return (
<div>
<h2>
{userName ? "Welcome, " + userName : "You are not Logged In"}
</h2>
{/* Add your home page content here */}
</div>
);
};
export default HomePage;
Step 12: Implementing Logout
Create a logout function that sends a POST request to
/api/logout
to clear the user session on the server side.Logout Function:
Define an asynchronous function named
logout
which will handle the logout functionality.Inside the
logout
function:Use
try-catch
block to handle potential errors.Make a POST request to
http://localhost:8000/api/logout
to log the user out.Check if the response is successful (HTTP status 200).
Log messages indicating whether the logout was successful or failed.
Navbar JSX:
Return JSX containing a navigation bar (
<nav>
element) with the following structure:A container (
<div>
element with class"container-fluid"
) holding the navbar content.A home link (
<a>
element with class"navbar-brand"
) pointing to"/"
.A list of navigation links (
<ul>
element with classes"navbar-nav me-auto mb-2 mb-md-0"
).Conditional rendering of navigation links based on the
userName
prop:If
userName
exists:- Render "Login" and "Register" links (
<li>
elements with corresponding<a>
elements).
- Render "Login" and "Register" links (
If
userName
does not exist:Render a "Logout" link (
<li>
element with an<a>
element).- Attach the
logout
function to theonClick
event of the "Logout" link.
- Attach the
import React from "react";
import "./navbar-fixed.css";
function Navbar({ userName }) {
const logout = async () => {
// Add async keyword here
try {
const response = await fetch(`http://localhost:8000/api/logout`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
});
if (response.ok) {
console.log("Logout successful");
} else {
console.error("Logout failed");
}
} catch (error) {
console.error("Error:", error);
}
};
return (
<nav className="navbar navbar-expand-md navbar-dark fixed-top bg-dark mb ">
<div className="container-fluid">
<a className="navbar-brand" href="/">
Home
</a>
<div>
<ul className="navbar-nav me-auto mb-2 mb-md-0">
{userName ? (
<>
<li className="nav-item">
<a className="nav-link" href="/login">
Login
</a>
</li>
<li className="nav-item">
<a className="nav-link" href="/register">
Register
</a>
</li>
</>
) : (
<li className="nav-item">
<a className="nav-link" href="/login" onClick={logout}>
Logout
</a>
</li>
)}
</ul>
</div>
</div>
</nav>
);
}
export default Navbar;
Step 13: Testing and Debugging
Thoroughly test the authentication flow, including login, registration, logout, and navigation.
Use browser developer tools to inspect network requests and troubleshoot any issues.
Ensure proper error handling and validation mechanisms are in place.
Step 14: Optimizing User Experience
Enhance the user interface and experience with appropriate feedback messages, loading indicators, and error alerts.
Continuously iterate and improve the authentication flow based on user feedback and testing results.
By following these steps, you can successfully set up user authentication in your React application, allowing users to securely log in, register, and navigate through different pages based on their authentication status. Building a robust authentication system not only enhances the security of your application but also improves the overall user experience.