In un precedente articolo abbiamo sviluppato una REST API per l'autenticazione tramite JWT in un ambiente React. Ciò sfruttando le potenzialità offerte da Laravel 11 e la libreria di terze parti tymondesigns/jwt-auth
. Questo approccio ci ha permesso di creare un back-end in grado di gestire il processo di autenticazione degli utenti. Una volta completata la fase di sviluppo, abbiamo verificato il funzionamento dell'applicazione utilizzando Postman per effettuare richieste HTTP e testare le risposte del server.
Ma passiamo ora alla creazione di un front-end che si interfacci con il back-end attraverso un form dedicato alla registrazione e all'autenticazione. Faremo uso della libreria React con cui costruire una UX fluida, consentendo agli utenti di interagire con il sistema in modo semplice e intuitivo.
Il progetto React
Innanzitutto assicuratevi di aver installato nella vostra macchina l'ambiente di runtime Node.JS per eseguire codice JavaScript lato server. Create poi una directory quindi da terminale puntate su di essa e digitate:
npm create vite@latest react-laravel-jwt -- --template react
Seguite la procedura rispondendo alle domande del sistema. Poi, sempre da terminale, digitate in sequenza:
cd react-laravel-jwt
npm install
npm run dev
Ora aprite il componente App.js
e ripulitelo come di seguito:
import './App.css';
function App() {
return (
Hello world!
);
}
export default App;
Con l'ultimo comando npm run dev
abbiamo servito l'applicazione su http://localhost:5173/
. Apritelo e vedrete al centro della pagina "Hello world".
Aggiungiamo Bootstrap al progetto
Diamo un po' di stile al progetto con il framework CSS Bootstrap. Da terminale digitate:
npm i bootstrap@5.3.3
Quindi importatelo nel componente App.js
:
import 'bootstrap/dist/css/bootstrap.min.css';
function App() {
return (
Hello world!
);
}
export default App;
Aprite il file index.html
e rimuovete il collegamento al file CSS index.css
. Quindi cancellate i file.
Bootstrap navBar
Andate su w3schools e scegliete il tutorial relativo a Bootstrap 5. Quindi dalla barra di sinistra scegliete NavBar:
Copiate il codice relativo alla prima navbar e convertitelo in JSX tramite il sito HTML to JSX. Poi incollatelo in App.js
, dovrete modificare i link come di seguito:
import 'bootstrap/dist/css/bootstrap.min.css';
function App() {
return (
<nav><a href="#">Logo</a>
<div id="mynavbar">
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Login</a></li>
<li><a href="#">Dashboard</a></li>
</ul>
<button type="button">Search</button>
</div>
</nav>;
}
export default App;
Installazione di react-router
Per il routing dell'applicazione, digitate da terminale:
npm i react-router-dom
Aprite main.js
ed importate il BrowserRouter
come di seguito:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import {BrowserRouter} from 'react-router-dom'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);
Ora passate a App.js
ed importate Route
, Routes
e Link
dalla libreria di terze parti react-router-dom
:
...
import {Route, Routes,Link} from 'react-router-dom'
...
Aggiungete il gruppo di rotte da gestire all'interno del div
di classe container
:
...
<div>}/></div>
);
}
export default App;
Creare il componente Home
Nella directory src
create la cartella components
, così da avere una struttura più ordinata:
Ecco il componente Home.js
:
import React, { Component } from 'react';
class Home extends Component {
render() {
return (
<div>
Hello world
</div>
);
}
}
export default Home;
Importatelo poi in App.js
, eccolo aggiornato:
import 'bootstrap/dist/css/bootstrap.min.css';
function App() {
return (
<nav><a href="#">Logo</a>
<div id="mynavbar">
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Login</a></li>
<li><a href="#">Dashboard</a></li>
</ul>
<button type="button">Search</button>
</div>
</nav>;
}
export default App;
Creazione degli altri componenti
Crete ora gli altri componenti sempre all'interno di components
, a cominciare da Login.js
:
import React, { Component } from 'react';
class Login extends Component {
render() {
return (
<div></div>
);
}
}
export default Login;
Passate poi a Dashboard.js
:
import React, { Component } from 'react';
class Dashboard extends Component {
render() {
return (
<div></div>
);
}
}
export default Dashboard;
Importazione componenti
Importateli entrambi in App.js
:
...
import Dashboard from './components/Dashboard';
import Home from './components/Home';
import Login from './components/Login';
...
ed aggiungeteli a Routes
:
<div className="container">
<Routes>
<Route path='/' element={<Home />} />
<Route path='/login' element={<Login />} />
<Route path='/dashboard' element={<Dashboard />} />
</Routes>
</div>
Cambiate anche il tag link
da a
a Link
come di seguito:
<li className="nav-item">
<Link className="nav-link active" to="/">Home</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/login">Login</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/dashboard">Dashboard</Link>
</li>
Infine, inserite un testo di riconoscimento nei componenti creati in precedenza:
import React, { Component } from 'react';
class Home extends Component {
render() {
return (
<div>
<h1>HELLO HOME</h1>
</div>
);
}
}
export default Home;
Fate la stessa cosa anche negli altri.
Aggiungere il form su Login.js
Navigate su w3schools.com/bootstrap5 scegliendo la parte relativa al form. Copiatevi il codice del primo esempio e, come in precedenza, convertitelo in jxs, quindi aggiungetelo in Login.js
:
import React from 'react';
const Login = () => {
return (
<div className='row justify-content-center pt-5'>
<div className="mb-3 mt-3">
<label htmlFor="email" className="form-label">Email:</label>
<input type="email" className="form-control" id="email" placeholder="Enter email" name="email" />
</div>
<div className="mb-3">
<label htmlFor="pwd" className="form-label">Password:</label>
<input type="password" className="form-control" id="pwd" placeholder="Enter password" name="pswd" />
</div>
<button type="button" className="btn btn-primary">Login</button>
</div>
);
};
export default Login;
bene quindi andiamo a gestirne lo stato con gli Hooks, sempre in login.js
modificate come di seguito:
import React, { useState } from 'react';
const Login = () => {
const [email,setEmail]=useState();
const [password,setPassword]=useState();
return (
...
Verificate di aver importato useState
da React, poi modificate i campi input
ed il button
:
import React, { useState } from 'react';
const Login = () => {
const [email,setEmail]=useState();
const [password, setPassword] = useState();
const submitForm =() => {
console.log(email + ' '+ password)
}
return (
<div className='row justify-content-center pt-5'>
<div className="mb-3 mt-3">
<label htmlFor="email" className="form-label">Email:</label>
<input onChange={e => setEmail(e.target.value)} type="email" className="form-control" id="email" placeholder="Enter email" name="email" />
</div>
<div className="mb-3">
<label htmlFor="pwd" className="form-label">Password:</label>
<input onChange={e => setPassword(e.target.value)} type="password" className="form-control" id="pwd" placeholder="Enter password" name="password" />
</div>
<button type="button" onClick={submitForm} className="btn btn-primary">Login</button>
</div>
);
};
export default Login;
Testate l'applicazione all'indirizzo http://localhost:5173/login
, inserite una email e la password nei relativi campi, date invio, quindi aprite la console.
Axios, Laravel e React
Per gestire le chiamate API useremo la libreria di terze parti Axios
. Quindi digitate da terminale:
npm i axios
Abituatevi ad organizzare il codice, sotto la directory src
create la cartella services
come da immagine:
Ora, all'interno di services
create il file (service) AuthUser.js
e digitate:
// services/AuthUser.js
import axios from 'axios';
const http = axios.create({
baseURL: "http://127.0.0.1:8000/api/auth"
headers: {
"Content-type": "application/json"
}
});
const AuthUser = {
http: http
};
export default AuthUser;
}
Importate il service AuthUser.js
in Login.js
e definite la chiamata submitForm
:
import React, { useState } from 'react';
import AuthUser from './../services/AuthUser';
const Login = () => {
const http = AuthUser.http;
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const submitForm = () => {
//console.log(email + ' '+ password)
http.post('/login', { email: email, password: password })
.then((res)=>{
console.log(res.data)
})
.catch((err) => {
console.error(err);
});
}
return (
<div className='row justify-content-center pt-5'>
<div className="mb-3 mt-3">
<label htmlFor="email" className="form-label">Email:</label>
<input onChange={e => setEmail(e.target.value)} type="email" className="form-control" id="email" placeholder="Enter email" name="email" />
</div>
<div className="mb-3">
<label htmlFor="pwd" className="form-label">Password:</label>
<input onChange={e => setPassword(e.target.value)} type="password" className="form-control" id="pwd" placeholder="Enter password" name="password" />
</div>
<button type="button" onClick={submitForm} className="btn btn-primary">Login</button>
</div>
);
};
export default Login;
Testate l'applicazione, inserite come al solito un'email e la password e ricordatevi di aprire la console:
Se avete inserito email e password esistenti verrà restituito un token valido.
Salvare il token nel localstorage
Nel service AuthUser.js
andrete a far sì che il token e lo User restituiti in fase di autenticazione vengano salvati in locale grazie al localStorage
. Apritelo e poi modificatelo come di seguito:
import { useState } from 'react';
import axios from 'axios';
const AuthUser = () => {
const [token, setToken] = useState(localStorage.getItem('token') || '');
const [user, setUser] = useState(localStorage.getItem('user') || '');
const saveToken = (user, token) => {
localStorage.setItem('token', JSON.stringify(token));
localStorage.setItem('user', JSON.stringify(user));
setToken(token);
setUser(user);
}
const http = axios.create({
baseURL: "http://127.0.0.1:8000/api/auth",
headers: {
"Content-type": "application/json",
"Authorization": `Bearer ${token}` // Aggiungi l'header Authorization con il token
}
});
return {
http,
saveToken,
token,
user
};
};
export default AuthUser;
Fatto questo adattate il componente Login.js
di conseguenza:
import React, { useState } from 'react';
import AuthUser from './../services/AuthUser';
const Login = () => {
const { http } = AuthUser(); // Destructuring per ottenere http da AuthUser
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const submitForm = () => {
//console.log(email + ' '+ password)
http.post('/login', { email: email, password: password })
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.error(err);
});
}
return (
<div className='row justify-content-center pt-5'>
<div className="mb-3 mt-3">
<label htmlFor="email" className="form-label">Email:</label>
<input onChange={e => setEmail(e.target.value)} type="email" className="form-control" id="email" placeholder="Enter email" name="email" />
</div>
<div className="mb-3">
<label htmlFor="pwd" className="form-label">Password:</label>
<input onChange={e => setPassword(e.target.value)} type="password" className="form-control" id="pwd" placeholder="Enter password" name="password" />
</div>
<button type="button" onClick={submitForm} className="btn btn-primary">Login</button>
</div>
);
};
export default Login;
Gestire il redirect con UseNavigate
Una volta settati il token e lo User nel sessionStorage
in locale non resta che gestire il redirect dello stesso grazie alla libreria useNavigate
di React:
import { useState } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
const AuthUser = () => {
const [token, setToken] = useState(localStorage.getItem('token') || '');
const [user, setUser] = useState(localStorage.getItem('user') || '');
const navigate = useNavigate();
const getToken =() => {
const tokenString = sessionStorage.getItem('token');
const userToken = JSON.parse(tokenString);
return userToken;
}
const getUser = () => {
const userString = sessionStorage.getItem('user');
const user_detail = JSON.parse(userString);
return user_detail;
}
const saveToken = (user, token) => {
localStorage.setItem('token', JSON.stringify(token));
localStorage.setItem('user', JSON.stringify(user));
setToken(token);
setUser(user);
navigate('/dashboard');
}
const http = axios.create({
baseURL: "http://127.0.0.1:8000/api/auth",
headers: {
"Content-type": "application/json",
"Authorization": `Bearer ${token}` // Aggiungi l'header Authorization con il token
}
});
return {
setToken:saveToken,
token,
user,
getToken,
http
};
};
export default AuthUser;
In questo modo avete realizzato i metodi che consentiranno di leggere i dati relativi al token ed allo user. Aprite poi il file Login.js
e modificatelo:
import React, { useState } from 'react';
import AuthUser from './../services/AuthUser';
const Login = () => {
const { http,setToken } = AuthUser(); // Destructuring per ottenere http da AuthUser
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const submitForm = () => {
//console.log(email + ' '+ password)
http.post('/login', { email: email, password: password })
.then((res) => {
setToken(res.data.user, res.data.access_token)
})
.catch((err) => {
console.error(err);
});
}
return (
<div className='row justify-content-center pt-5'>
<div className="mb-3 mt-3">
<label htmlFor="email" className="form-label">Email:</label>
<input onChange={e => setEmail(e.target.value)} type="email" className="form-control" id="email" placeholder="Enter email" name="email" />
</div>
<div className="mb-3">
<label htmlFor="pwd" className="form-label">Password:</label>
<input onChange={e => setPassword(e.target.value)} type="password" className="form-control" id="pwd" placeholder="Enter password" name="password" />
</div>
<button type="button" onClick={submitForm} className="btn btn-primary">Login</button>
</div>
);
};
export default Login;
Eseguite ora il login. Con le credenziali corrette verrete reindirizzati alla Dashboard ed il token e l'oggetto User
verranno salvati nel localstorage
:
Realizzare il componente Navbar
Il prossimo passaggio consiste nell'inerimento della Navbar in un suo componente. Create la cartella shared
in src
e al suo interno create Navbar.jsx
. Apritelo ed inserite la parte del tag nav
:
import React, { Component } from "react";
import {Link} from 'react-router-dom'
class Navbar extends Component {
render() {
return (
<div>
<nav className="navbar navbar-expand-sm navbar-dark bg-dark">
<div className="container-fluid">
<a className="navbar-brand" href="#">
Logo
</a>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#mynavbar"
>
<span className="navbar-toggler-icon" />
</button>
<div className="collapse navbar-collapse" id="mynavbar">
<ul className="navbar-nav me-auto">
<li className="nav-item">
<Link className="nav-link active" to="/">
Home
</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/login">
Login
</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/dashboard">
Dashboard
</Link>
</li>
</ul>
</div>
</div>
</nav>
</div>
);
}
}
export default Navbar;
Di conseguenza il componente App.js
si presenterà così:
import 'bootstrap/dist/css/bootstrap.min.css';
import { Route, Routes} from 'react-router-dom'
import Dashboard from './components/Dashboard';
import Home from './components/Home';
import Login from './components/Login';
import Navbar from './shared/Navbar';
function App() {
return (
<div>
<Navbar/>
<div className="container">
<Routes>
<Route path='/' element={<Home />} />
<Route path='/login' element={<Login />} />
<Route path='/dashboard' element={<Dashboard />} />
</Routes>
</div>
</div>
);
}
export default App;
Ricordatevi inoltre di importare in Navbar.js
il codice import {Link} from 'react-router-dom'
.
Modificare Navbar in caso di autenticazione
Nel caso in cui siate loggati, non avrebbe senso mostrare nella navbar il collegamento a login e Dashboard. Quindi farete in modo che, una volta autenticati, dal menu non sarà visibile il login, mostrerete invece logout e Dashboard. Quindi, all'interno della directory shared
create due componenti Guest.jsx
ed Auth.jsx
. Aprite poi il primo e modificatelo come segue:
import React from 'react';
import { Link, Route, Routes } from 'react-router-dom';
import Login from '../components/Login';
import Register from '../components/Register';
import Home from './../components/Home';
const Guest = () => {
return (
<div>
<nav className="navbar navbar-expand-sm navbar-dark bg-dark">
<div className="container-fluid">
<Link className="navbar-brand" to="/">Logo</Link>
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mynavbar">
<span className="navbar-toggler-icon" />
</button>
<div className="collapse navbar-collapse" id="mynavbar">
<ul className="navbar-nav me-auto">
<li className="nav-item">
<Link className="nav-link active" to="/">Home</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/login">Login</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/register">Register</Link>
</li>
</ul>
<form className="d-flex">
<input className="form-control me-2" type="text" placeholder="Search" />
<button className="btn btn-primary" type="button">Search</button>
</form>
</div>
</div>
</nav>
<div className="container">
<Routes>
<Route path='/' element={<Home />} />
<Route path='/login' element={<Login />} />
<Route path='/register' element={<Register />} />
</Routes>
</div>
</div>
);
};
export default Guest;
In pratica avete copiato il contenuto di Navbar
per poi incollarlo modificando le voci relative ai link per l'utente non loggato. Cancellate il componente Navbar.jsx
, non serve più. Quindi modificate Auth.jsx
così:
import React from 'react';
import { Link, Route, Routes } from 'react-router-dom';
import Home from '../components/Home';
import Dashboard from './../components/Dashboard';
const Auth = () => {
return (
<div>
<nav className="navbar navbar-expand-sm navbar-dark bg-dark">
<div className="container-fluid">
<Link className="navbar-brand" to="/">Logo</Link>
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mynavbar">
<span className="navbar-toggler-icon" />
</button>
<div className="collapse navbar-collapse" id="mynavbar">
<ul className="navbar-nav me-auto">
<li className="nav-item">
<Link className="nav-link active" to="/">Home</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/dashboard">Dashboard</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/login">Logout</Link>
</li>
</ul>
<form className="d-flex">
<input className="form-control me-2" type="text" placeholder="Search" />
<button className="btn btn-primary" type="button">Search</button>
</form>
</div>
</div>
</nav>
<div className="container">
<Routes>
<Route path='/' element={<Home />} />
<Route path='/dashboard' element={<Dashboard />} />
</Routes>
</div>
</div>
);
};
export default Auth;
Ripulite il componente App.jsx
ed importate i componenti realizzati in precedenza:
import 'bootstrap/dist/css/bootstrap.min.css';
import AuthUser from './services/AuthUser';
import Auth from './shared/Auth';
import Guest from './shared/Guest';
function App() {
const { getToken } = AuthUser();
if (!getToken()) {
return (
<Guest />
);
}
return (
<Auth />
);
}
export default App;
Come potete notare, avete importato anche il metodo getToken
dal service AuthUser
. Si può così stabilire se esiste o meno un token valido relativo ad un utente e quindi mostrare il menu Guest o Auth.
Componente Register
Testando ora l'applicazione avrete sicuramente un errore in quanto manca il componente Register.jsx
. Per cui implementatelo all'interno della cartella components
ed inserite il codice seguente:
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import AuthUser from '../services/AuthUser';
const Register = () => {
const navigate = useNavigate();
const { http } = AuthUser();
const [email, setEmail] = useState();
const [name, setName] = useState();
const [password, setPassword] = useState();
const [passwordConf, setPasswordConf] = useState();
const submitForm = () => {
http.post('/register', { name: name, email: email, password: password, password_confirmation: passwordConf })
.then((res) => {
navigate('/login')
})
}
return (
<div className='row justify-content-center pt-5'>
<div className="mb-3 mt-3">
<label htmlFor="name" className="form-label">Name:</label>
<input onChange={e => setName(e.target.value)} type="email" className="form-control" id="name" placeholder="Enter name" name="name" />
</div>
<div className="mb-3 mt-3">
<label htmlFor="email" className="form-label">Email:</label>
<input onChange={e => setEmail(e.target.value)} type="email" className="form-control" id="email" placeholder="Enter email" name="email" />
</div>
<div className="mb-3">
<label htmlFor="password" className="form-label">Password:</label>
<input onChange={e => setPassword(e.target.value)} type="password" className="form-control" id="password" placeholder="Enter password" name="password" />
</div>
<div className="mb-3">
<label htmlFor="password_confirmation" className="form-label">Password confirmation:</label>
<input onChange={e => setPasswordConf(e.target.value)} type="password" className="form-control" id="password_confirmation" placeholder="Enter password confirmation" name="password_confirmation" />
</div>
<button type="button" onClick={submitForm} className="btn btn-primary">Register</button>
</div>
);
};
export default Register;
Innanzitutto importate useNavigate
da react-router-dom
per il redirect alla pagina di login, quindi definite gli stati, al momento vuoti, di name
e password_confirmation
essenziali ai fini della registrazione, aggiungete inoltre i relativi campi. Fatto questo navigate all'indirizzo http://localhost:3000/register
e provate a registrarvi. Se verrete reindirizzati su Login
il codice è corretto.
Il logout
Per il logout modificate il menu in Auth.js
in quanto non dovrete creare nessun componente relativo al logout. Basta un metodo che cancelli il token:
<li className="nav-item">
<Link className="nav-link" to="/dashboard">Dashboard</Link>
</li>
<li className="nav-item">
<span role='button' className="nav-link" onClick={logoutUser}>Logout</span>
</li>
Implementate poi il metodo logoutUser
:
const Auth = () => {
const {token, logout} = AuthUser();
const logoutUser =() => {
if(!token !== undefined){
logout();
}
}
Avete così importato i metodi token
e logout
da authUser
. Il secondo però dovete ancora crearlo, per cui aprite AuthUser
per realizzarlo:
...
const saveToken = (user, token) => {
sessionStorage.setItem('token', JSON.stringify(token));
sessionStorage.setItem('user', JSON.stringify(user));
setToken(token);
setUser(user);
navigate('/dashboard');
}
const logout =() => {
sessionStorage.clear();
navigate('/login');
}
...
return {
setToken:saveToken,
token,
user,
getToken,
logout,
http
}
Dopo averlo creato non scordatevi di esportarlo.
La Dashboard
Aprite il componente Dashboard
e modificatelo così:
import React from 'react';
import AuthUser from '../services/AuthUser';
const Dashboard = () => {
const {user} = AuthUser();
return (
<div>
HELLO <small><b>{user.name}</b> your email is <b>{user.email}</b></small>
</div>
);
};
export default Dashboard;
Importate infine il metodo user
da AuthUser
che contiene le informazioni riguardanti l'utente loggato e mostratelo a video grazie all'interpolazione.
Conclusione: il nostro progetto con Laravel
Nel nostro progetto ci siamo concentrati sull'implementazione di un sistema di autenticazione utilizzando React. Durante il processo di autenticazione abbiamo gestito diverse fasi cruciali, come la verifica delle credenziali e la generazione di un token di accesso da parte dell'API. Questo token è fondamentale per consentire agli utenti di accedere alle risorse protette dell'applicazione. Una volta ottenuto il token, abbiamo adottato una pratica sicura salvandolo nel sessionStorage
del browser. Questo garantisce che esso rimanga accessibile solo durante la sessione dell'utente e non venga conservato dopo la chiusura del browser.