Sick Gaming
[Tut] React JWT Authentication Tutorial with PHP Backend (Login, Register & Protecte - Printable Version

+- Sick Gaming (https://www.sickgaming.net)
+-- Forum: Programming (https://www.sickgaming.net/forum-76.html)
+--- Forum: PHP Development (https://www.sickgaming.net/forum-82.html)
+--- Thread: [Tut] React JWT Authentication Tutorial with PHP Backend (Login, Register & Protecte (/thread-103618.html)



[Tut] React JWT Authentication Tutorial with PHP Backend (Login, Register & Protecte - xSicKxBot - 12-07-2025

[Tut] React JWT Authentication Tutorial with PHP Backend (Login, Register & Protecte

<div style="margin: 5px 5% 10px 5%;"><img src="https://www.sickgaming.net/blog/wp-content/uploads/2025/12/react-jwt-authentication-tutorial-with-php-backend-login-register-protected-routes.jpg" width="550" height="288" title="" alt="" /></div><div><div class="modified-on" readability="7.1489361702128"> by <a href="https://phppot.com/about/">Vincy</a>. Last modified on November 28th, 2025.</div>
<p>The JWT authentication is a secure way of implementing a web login process without session management. This tutorial is for implementing React JWT authentication with PHP. It has the following steps to understand the process easily.</p>
<p>Additionally, this example code provides a <a href="https://phppot.com/php/user-registration-in-php-with-login-form-with-mysql-and-code-download/">user registration code</a> to create new user to the database. With the registration and login code you are getting a base for your websites authentication module from this tutorial.</p>
<ol>
<li>React login form submits the registered username and password.</li>
<li>PHP backend script validates the login details with the database.</li>
<li>PHP login success case generates JWT signed encoded user profile data.</li>
<li>React frontend receives the JWT token and stores to localStorage.</li>
<li>React validates the existence of the token with the ProtectedRoutes component.</li>
<li>If the ProtectedRoutes failed to find the JWT token, it denies the access.</li>
</ol>
<p><img decoding="async" loading="lazy" class="alignnone size-large wp-image-24480" src="https://phppot.com/wp-content/uploads/2025/10/react-jwt-authentication-php-backend-login-register-550x288.jpeg" alt="React Jwt Authentication Php Backend Login Register" width="550" height="288" srcset="https://phppot.com/wp-content/uploads/2025/10/react-jwt-authentication-php-backend-login-register-550x288.jpeg 550w, https://phppot.com/wp-content/uploads/2025/10/react-jwt-authentication-php-backend-login-register-300x157.jpeg 300w, https://phppot.com/wp-content/uploads/2025/10/react-jwt-authentication-php-backend-login-register-768x402.jpeg 768w, https://phppot.com/wp-content/uploads/2025/10/react-jwt-authentication-php-backend-login-register.jpeg 1200w" sizes="auto, (max-width: 550px) 100vw, 550px"></p>
<h2>React User Registration</h2>
<p>This registration code helps to create data for your login process. Also, with registration this authentication example will be a more complete version to deploy in your application.</p>
<p>This example has a registration form with very few fields. <a href="https://phppot.com/react/react-hook-user-registration-form/">React builds the formData</a> state when the user enters the data. On submit, it passes the entered data to the PHP page <code>register-action.php</code>. It stores the user login name, email and the password to the database.</p>
<p>In the below JSX script, the formData, success/error state variables are managed. The handleSubmit hook handles the <a href="https://phppot.com/php/bootstrap-contact-form-with-javascript-validation-and-php/">client-side form validation</a>. This hook posts the data to the server once the validation returns true.</p>
<p class="code-heading"><code>Part of RegisterForm.jsx with React states and hooks</code></p>
<pre class="prettyprint"><code class="language-jsx">import axios from "axios";
import { useNavigate } from "react-router-dom";
import SERVER_SIDE_API_ROOT from "../config";
import "../styles/style.css";
const RegisterForm = () =&gt; { const [formData, setFormData] = useState({ username: "", email: "", password: "", confirmPassword: "", }); const [errorMessage, setErrorMessage] = useState(""); const [successMessage, setSuccessMessage] = useState(""); const navigate = useNavigate(); const handleSubmit = async (e) =&gt; { e.preventDefault(); const { username, email, password, confirmPassword } = formData; if (!username || !email || !password || !confirmPassword) { setErrorMessage("Please fill in all fields"); setSuccessMessage(""); return; } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { setErrorMessage("Please enter a valid email address"); return; } if (password.length &lt; 6) { setErrorMessage("Password must be at least 6 characters"); return; } if (password !== confirmPassword) { setErrorMessage("Passwords do not match"); setSuccessMessage(""); return; } try { const res = await axios.post( `${SERVER_SIDE_API_ROOT}/registration-action.php`, { username, email, password }, { headers: { "Content-Type": "application/json" } } ); console.log("Server response:", res.data); if (res.data.status === "success") { setSuccessMessage(res.data.message); setErrorMessage(""); setFormData({ username: "", email: "", password: "", confirmPassword: "", }); setTimeout(() =&gt; { navigate("/login"); }, 2000); } else { setErrorMessage(res.data.message || "Registration failed"); setSuccessMessage(""); } } catch (err) { console.error("Axios Error:", err); setErrorMessage("Server error or CORS issue"); setSuccessMessage(""); }
};
</code></pre>
<p><img decoding="async" loading="lazy" class="alignnone wp-image-24459 size-medium" src="https://phppot.com/wp-content/uploads/2025/10/react-jwt-registration-230x300.jpg" alt="react jwt registration" width="230" height="300" srcset="https://phppot.com/wp-content/uploads/2025/10/react-jwt-registration-230x300.jpg 230w, https://phppot.com/wp-content/uploads/2025/10/react-jwt-registration-550x717.jpg 550w, https://phppot.com/wp-content/uploads/2025/10/react-jwt-registration.jpg 651w" sizes="auto, (max-width: 230px) 100vw, 230px"></p>
<p class="code-heading"><code>Returning Form UI code with RegisterForm.jsx</code></p>
<pre class="prettyprint"><code class="language-jsx">&lt;div className="admin-wrapper"&gt; &lt;div className="card-container" style={{ maxWidth: "400px", margin: "95px auto" }}&gt; &lt;h2&gt;User Registration&lt;/h2&gt; &lt;form onSubmit={handleSubmit}&gt; &lt;div&gt; &lt;label&gt;Username:&lt;/label&gt; &lt;input value={formData.username} onChange={(e) =&gt; setFormData({ ...formData, username: e.target.value }) }/&gt; &lt;/div&gt; &lt;div&gt; &lt;label&gt;Email:&lt;/label&gt; &lt;input type="email" value={formData.email} onChange={(e) =&gt; setFormData({ ...formData, email: e.target.value }) } /&gt; &lt;/div&gt; &lt;div&gt; &lt;label&gt;Password:&lt;/label&gt; &lt;input type="password" value={formData.password} onChange={(e) =&gt; setFormData({ ...formData, password: e.target.value }) } /&gt; &lt;/div&gt; &lt;div&gt; &lt;label&gt;Confirm Password:&lt;/label&gt; &lt;input type="password" value={formData.confirmPassword} onChange={(e) =&gt; setFormData({ ...formData, confirmPassword: e.target.value, }) }/&gt; &lt;/div&gt; {errorMessage &amp;&amp; ( &lt;div className="alert alert-danger" role="alert"&gt; {errorMessage} &lt;/div&gt; )} {successMessage &amp;&amp; ( &lt;div className="alert alert-success" role="alert"&gt; {successMessage} &lt;/div&gt; )} &lt;button type="submit"&gt;Register&lt;/button&gt; &lt;p style={{ textAlign: "center", marginTop: "10px" }}&gt; Already have an account?{" "} &lt;a href="/login" style={{ color: "#232323", fontWeight: "600",textDecoration: "none" }}&gt; Login here &lt;/a&gt; &lt;/p&gt; &lt;/form&gt; &lt;/div&gt; &lt;/div&gt;
);
};
export default RegisterForm;
</code></pre>
<p>PHP backend handles the registration request and stores the entered password in an encrypted form. It validates the user email uniqueness. If the entered email is already in the database, then this code rejects the user’s insertion.</p>
<p class="code-heading"><code>react-jwt-login-register-api/registration-action.php</code></p>
<pre class="prettyprint"><code class="language-php">&lt;?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Content-Type: application/json"); if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
include "db.php";
$input = file_get_contents("php://input");
$data = json_decode($input);
if ( !isset($data-&gt;username) || !isset($data-&gt;email) || !isset($data-&gt;password)
) { echo json_encode(["status" =&gt; "error", "message" =&gt; "Invalid input"]); exit;
}
$username = mysqli_real_escape_string($conn, $data-&gt;username);
$email = mysqli_real_escape_string($conn, $data-&gt;email);
$password = password_hash($data-&gt;password, PASSWORD_BCRYPT);
$check = $conn-&gt;query("SELECT * FROM users WHERE email='$email'");
if ($check-&gt;num_rows &gt; 0) { echo json_encode(["status" =&gt; "error", "message" =&gt; "Email already exists"]); exit;
}
$sql = "INSERT INTO users (username, email, password) VALUES ('$username', '$email', '$password')";
if ($conn-&gt;query($sql) === TRUE) { echo json_encode(["status" =&gt; "success", "message" =&gt; "Registration successful"]);
} else { echo json_encode(["status" =&gt; "error", "message" =&gt; $conn-&gt;error]);
}
?&gt;
</code></pre>
<h2>React login JWT authentication</h2>
<p>If the user submits the correct username and password, the client receives success response from the server. The response will contain a JWT signed user profile data array.</p>
<p>In the client-side, the <strong>jwt-decode</strong> is used to decode the JWT response. The jwt-decode is the JavaScript library which can read and decode the JWT encoded string.</p>
<p>Run the following command to install this library into the application repository.</p>
<pre class="prettyprint"><code class="language-makefile">npm install jwt-decode [OR] yarn add jwt-decode
</code></pre>
<p><img decoding="async" loading="lazy" class="alignnone wp-image-24457 size-medium" src="https://phppot.com/wp-content/uploads/2025/10/react-jwt-login-300x295.jpg" alt="react jwt login" width="300" height="295" srcset="https://phppot.com/wp-content/uploads/2025/10/react-jwt-login-300x295.jpg 300w, https://phppot.com/wp-content/uploads/2025/10/react-jwt-login-550x541.jpg 550w, https://phppot.com/wp-content/uploads/2025/10/react-jwt-login-768x755.jpg 768w, https://phppot.com/wp-content/uploads/2025/10/react-jwt-login.jpg 830w" sizes="auto, (max-width: 300px) 100vw, 300px"></p>
<p>In the below script, if the response status is success it sets the JWT authentication token to a JavaScript localStorage. Then, it redirects to the dashboard. The dashboard is a protected page that can be accessed if a user is JWT-authenticated.</p>
<p class="code-heading"><code>src/components/LoginForm.jsx</code></p>
<pre class="prettyprint"><code class="language-jsx">import React, { useState } from "react";
import { Link } from "react-router-dom";
import SERVER_SIDE_API_ROOT from "../config";
import "../styles/style.css";
import axios from "axios"; const LoginForm = () =&gt; { const [formData, setFormData] = useState({ username: "", password: "", }); const [errorMessage, setErrorMessage] = useState(""); const handleLogin = (e) =&gt; { e.preventDefault(); const { username, password } = formData; if (!username || !password) { setErrorMessage("Please enter both username and password"); return; } axios.post(`${SERVER_SIDE_API_ROOT}/login-action.php`, { username, password, }) .then((res) =&gt; { if (res.data.status === "success") { localStorage.setItem("token", res.data.token); setErrorMessage(""); setTimeout(() =&gt; { window.location.href = "/dashboard"; }, 1000); } else { setErrorMessage(res.data.message); } }) .catch((err) =&gt; { setErrorMessage("Server error: " + err.message); });
};
return (
&lt;div className="admin-wrapper"&gt; &lt;div className="card-container" style={{ maxWidth: "400px", margin: "154px auto" }}&gt; &lt;h2&gt;Login&lt;/h2&gt; &lt;form onSubmit={handleLogin}&gt; &lt;div&gt; &lt;label&gt;Username:&lt;/label&gt; &lt;input value={formData.username} onChange={(e) =&gt; setFormData({ ...formData, username: e.target.value })} /&gt; &lt;/div&gt; &lt;div&gt; &lt;label&gt;Password:&lt;/label&gt; &lt;input type="password" value={formData.password} onChange={(e) =&gt; setFormData({ ...formData, password: e.target.value }) } /&gt; &lt;/div&gt; {errorMessage &amp;&amp; ( &lt;div className="alert alert-danger" role="alert"&gt; {errorMessage} &lt;/div&gt; )} &lt;button type="submit"&gt;Login&lt;/button&gt; &lt;p style={{ textAlign: "center", marginTop: "10px" }}&gt; Don’t have an account?{" "} &lt;Link to="/registerform" style={{ color: "#232323", fontWeight: "600", textDecoration: "none" }}&gt; Register here &lt;/Link&gt; &lt;/p&gt; &lt;/form&gt; &lt;/div&gt;
&lt;/div&gt;
);
};
export default LoginForm;
</code></pre>
<p>In PHP the <code>firebase/php-jwt</code> library is installed by using this composer command. Once the logged-in details matched, this library is used to generate the JWT token with the profile array.</p>
<pre class="prettyprint"><code class="language-makefile">composer require firebase/php-jwt
</code></pre>
<p class="code-heading"><code>react-jwt-login-register-api/login-action.php</code></p>
<pre class="prettyprint"><code class="language-php">&lt;?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Content-Type: application/json"); include "db.php";
require 'vendor/autoload.php'; use Firebase\JWT\JWT;
use Firebase\JWT\Key; $secret_key = "MY_SECRET_KEY_12345"; if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { http_response_code(200); exit;
}
$data = json_decode(file_get_contents("php://input"));
if (!isset($data-&gt;username) || !isset($data-&gt;password)) { echo json_encode(["status" =&gt; "error", "message" =&gt; "Missing credentials"]); exit;
}
$username = $conn-&gt;real_escape_string($data-&gt;username);
$password = $data-&gt;password;
$result = $conn-&gt;query("SELECT * FROM users WHERE username = '$username' LIMIT 1");
if ($result-&gt;num_rows === 0) { echo json_encode(["status" =&gt; "error", "message" =&gt; "Invalid Username or Password"]); exit;
}
$user = $result-&gt;fetch_assoc();
if (!password_verify($password, $user['password'])) { echo json_encode(["status" =&gt; "error", "message" =&gt; "Invalid password"]); exit;
}
$payload = [ "iss" =&gt; "http://localhost", "aud" =&gt; "http://localhost", "iat" =&gt; time(), "exp" =&gt; time() + (60 * 60), "user" =&gt; [ "id" =&gt; $user['id'], "username" =&gt; $user['username'], "email" =&gt; $user['email'] ]
];
$jwt = JWT::encode($payload, $secret_key, 'HS256');
echo json_encode([ "status" =&gt; "success", "message" =&gt; "Login successful", "token" =&gt; $jwt
]);
?&gt;
</code></pre>
<h2>Protected Route Component that validates JWT token to permit</h2>
<p>This component validates if the localStorage has the JWT token. If not, then it will stop the user from accessing a requested page. Instead, it redirects to the login page.</p>
<p class="code-heading"><code>src/components/ProtectedRoute.js</code></p>
<pre class="prettyprint"><code class="language-jsx">import { Navigate } from "react-router-dom"; const ProtectedRoute = ({ children }) =&gt; { const token = localStorage.getItem("token"); if (!token) { return &lt;Navigate to="/login" replace /&gt;; } return children;
}; export default ProtectedRoute; </code></pre>
<h2>Dashboard with profile information</h2>
<p>The React main App component imports the ProtectedRoutes to bring the validation process at the top layer. This ProtectedRoutes wrapper around the Dashboard will help to permit only a JWT-authenticated user to the dashboard.</p>
<p>This <a href="https://phppot.com/react/react-dynamic-dashboard-tutorial/">React dashboard script</a> gets the user details from the decoded JWT token. Then, those details are rendered to the UI as shown below.</p>
<p>A simple header is created for this dashboard view with a logout option. On clicking logout, it clears the localStorage and redirects to the login page.</p>
<p><img decoding="async" loading="lazy" class="alignnone size-large wp-image-24460" src="https://phppot.com/wp-content/uploads/2025/10/react-jwt-dashboard-550x325.jpg" alt="react jwt dashboard" width="550" height="325" srcset="https://phppot.com/wp-content/uploads/2025/10/react-jwt-dashboard-550x325.jpg 550w, https://phppot.com/wp-content/uploads/2025/10/react-jwt-dashboard-300x177.jpg 300w, https://phppot.com/wp-content/uploads/2025/10/react-jwt-dashboard-768x454.jpg 768w, https://phppot.com/wp-content/uploads/2025/10/react-jwt-dashboard.jpg 1532w" sizes="auto, (max-width: 550px) 100vw, 550px"></p>
<p class="code-heading"><code>src/pages/Dashboard.jsx</code></p>
<pre class="prettyprint"><code class="language-jsx">import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { jwtDecode } from "jwt-decode";
import "../styles/style.css"; const Dashboard = () =&gt; { const [user, setUser] = useState(null); const navigate = useNavigate(); useEffect(() =&gt; { const token = localStorage.getItem("token"); if (!token) { navigate("/login"); return; } try { const decoded = jwtDecode(token); setUser(decoded.user); } catch (error) { console.error("Invalid token:", error); localStorage.removeItem("token"); navigate("/login"); } }, [navigate]); if (!user) return &lt;p&gt;Loading...&lt;/p&gt;; const handleLogout = () =&gt; { localStorage.removeItem("token"); navigate("/login"); }; return ( &lt;div className="dashboard-wrapper"&gt; &lt;nav className="navbar"&gt; &lt;div className="navbar-left"&gt; &lt;h2&gt;Dashboard&lt;/h2&gt; &lt;/div&gt; &lt;div className="navbar-right"&gt; &lt;button className="logout-btn" onClick={handleLogout}&gt; &lt;img src="./logout.svg" alt="Logout" className="logout-icon" /&gt; &lt;/button&gt; &lt;/div&gt; &lt;/nav&gt; &lt;div className="card"&gt; &lt;img src="/profile.jpg" alt="" className="profile-pic" /&gt; &lt;h2&gt;Welcome, {user.username}&lt;/h2&gt; &lt;p&gt; &lt;strong&gt;Email:&lt;/strong&gt; {user.email} &lt;/p&gt; &lt;/div&gt; &lt;/div&gt; );
};
export default Dashboard;
</code></pre>
<h2>conclusion</h2>
<p>With this login authentication base, you can have a good start to create a React JWT login. The user registration involves PHP and MySQL connection to generate backend data for this login. It can be enhanced by adding server-side JWT validation to improve the security. The concept of securing application pages with protected routes helps to reuse the wrapper for more components.</p>
<p><strong>References:</strong></p>
<ol>
<li><a href="https://www.jwt.io/introduction" target="_blank" rel="noopener">About JWT (JSON Web Token)</a></li>
<li><a href="https://github.com/firebase/php-jwt" target="_blank" rel="noopener">PHP JWT backend library to encode decode JWT token</a></li>
<li><a href="https://www.npmjs.com/package/jwt-decode" target="_blank" rel="noopener">Client side JWT decoding library</a></li>
</ol>
<p><a class="download" href="https://phppot.com/downloads/react/react-jwt-authentication-php-backend-login-register.zip">Download</a></p>
<div class="written-by" readability="9.8427672955975">
<div class="author-photo"> <img loading="lazy" decoding="async" src="https://phppot.com/wp-content/themes/solandra/images/Vincy.jpg" alt="Vincy" width="100" height="100" title="Vincy"> </div>
<div class="written-by-desc" readability="14.764150943396"> Written by <a href="https://phppot.com/about/">Vincy</a>, a web developer with 15+ years of experience and a Masters degree in Computer Science. She specializes in building modern, lightweight websites using PHP, JavaScript, React, and related technologies. Phppot helps you in mastering web development through over a decade of publishing quality tutorials. </div>
</p></div>
<p> <!-- #comments --> </p>
<div class="related-articles">
<h2>Related Tutorials</h2>
</p></div>
<p> <a href="https://phppot.com/react/react-jwt-authentication-php-backend-login-register/#top" class="top">↑ Back to Top</a> </p>
</div>


https://www.sickgaming.net/blog/2025/10/30/react-jwt-authentication-tutorial-with-php-backend-login-register-protected-routes/