A simple JWT example with NodeJS and Python (part 2)
A continuation of https://blogg.fsh.se/2022/06/06/a-simple-jwt-example-with-nodejs-and-python-part-1/
This article describes the creation of a simple React client logging into the backend using JWT.
The React client
Keep track of the JWT token in a state variable. This would be better saved in the browsers local storage,
import './App.css'; import { useState } from 'react'; import "./styles.css"; function App() { const [token, setToken] = useState(0) const [text, setText] = useState({ text: "", color: "black" })
Handler code for the submit button. Standard fetch() with a handler that receives the JWT token.
const handleSubmit = async (event) => { event.preventDefault(); console.log(event); // Prevent page reload const email_value = event.target.name.value const password_value = event.target.password.value const info = { email: email_value, password: password_value } try { await fetch("http://192.168.1.67:5000/login", { method: "POST", body: JSON.stringify(info), headers: { "Content-Type": "application/json;charset=UTF-8", "Accept": "application/json" } }) .then((response) => { const statusCode = response.status; const data = response.json(); return Promise.all([statusCode, data]); }) .then((data) => { // This is how the backend returns the data data = data[1]; // You could also check the status code here but the token is important if (data.access_token) { setToken(data.access_token); // localStorage.setItem("access_token", data.access_token); // Better! setText({ text: "Sucessfully logged in!", color: "green" }); } else { setText({ text: "Incorrect email or password", color: "red" }); } }); } catch (err) { console.error(err); } };
Code for handling a click on the Profile button. Just makes a call to a protected entry point so a valid JWT token is needed!
const handleProfileClick = (event) => { event.preventDefault(); try { fetch("http://192.168.1.67:5000/profile", { method: "GET", headers: { "Content-Type": "application/json;charset=UTF-8", "Accept": "application/json", "Authorization": "Bearer " + token } }) .then((response) => { const statusCode = response.status; console.log(statusCode) const data = response.json(); console.log(data) return Promise.all([statusCode, data]); }) .then((data) => { console.log(data); data = data[1] if (data.about) { setText({ text: data.about, color: "black" }) } }); } catch (err) { console.error(err); } };
Handler for the Logout buttton. The backend will kill the token, making it ununsable.
const handleLogout = (event) => { event.preventDefault(); try { fetch("http://192.168.1.67:5000/logout", { method: "POST", headers: { "Authorization": "Bearer " + token } }) .then((response) => { const statusCode = response.status; const data = response.json(); return Promise.all([statusCode, data]); }) .then((data) => { console.log(data) localStorage.removeItem("access_token"); setToken(0); setText({ text: "Succesfully logged out!", color: "green" }) }) } catch (err) { console.log(err); } };
Renderers of the logged in state (just a test message) and the not-logged-in state (a login form)
The styles CSS that makes the login form nice to look at!
styles.css
.app { font-family: sans-serif; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 20px; height: 100vh; font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif; background-color: #f8f9fd; } input[type="text"], input[type="password"] { height: 25px; border: 1px solid rgba(0, 0, 0, 0.2); } input[type="submit"] { margin-top: 10px; cursor: pointer; font-size: 15px; background: #01d28e; border: 1px solid #01d28e; color: #fff; padding: 10px 20px; } input[type="submit"]:hover { background: #6cf0c2; } .button-container { display: flex; justify-content: center; } .login-form { background-color: white; padding: 2rem; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); } .list-container { display: flex; } .error { color: red; font-size: 12px; } .title { font-size: 25px; margin-bottom: 20px; } .input-container { display: flex; flex-direction: column; gap: 8px; margin: 10px; }
const RenderLoggedInState = () => { return <div> <h1>You are logged in!</h1> <button onclick="{handleProfileClick}">Profile</button> <button onclick="{handleLogout}">Logout</button> </div> } const renderForm = () => { return <div classname="login-form"> <div classname="form"> <div classname="title">Sign In</div> <form onsubmit="{handleSubmit}"> <div classname="input-container"> <label>Username </label> <input type="text" name="name"> </div> <div classname="input-container"> <label>Password</label> <input type="password" name="password"> </div> <div classname="button-container"> <input type="submit" value="Login"> </div> </form> </div> <div> <h5>Do you have an invitation code? If so, signup here!</h5> </div> </div> } return (<div classname="app"> {token === 0 ? renderForm() : RenderLoggedInState()} {text.text} </div>); } export default App;
Complete code found here: https://github.com/fshsweden/login-frontend-test