Authentification
L'authentification joue un rôle crucial dans la sécurité et la protection des données des utilisateurs, en vérifiant leur identité avant de leur accorder l'accès à des fonctionnalités restreintes. Dans cette leçon, nous allons utiliser la plateforme populaire Firebase de Google pour gérer notre authentification, autant dans l'application React que pour notre API.
Installation dans React
-
Installer les modules pour Firebase dans React :
-
Configurer Firebase :
Aller à la console de Firebase.
Ajouter un nouveau projet :
Ajouter une application :
Configurer l'authentification par courriel/mot de passe :
Ajouter un utilisateur :
-
Créer le fichier firebase.ts :
firebase.tsimport { initializeApp } from 'firebase/app'; import { getAuth, signInWithEmailAndPassword, signOut } from 'firebase/auth'; const firebaseConfig = { apiKey: '', authDomain: '', projectId: '', storageBucket: '', messagingSenderId: '', appId: '', }; // Initialize Firebase const app = initializeApp(firebaseConfig); export const auth = getAuth(app); export const logInWithEmailAndPassword = async ( email: string, password: string ) => { try { await signInWithEmailAndPassword(auth, email, password); } catch (err: any) { console.error(err); alert(err.message); } };
-
Créer une page de login
routes/login.route.tsx/** * Basé sur le modèle de Material UI * https://github.com/mui/material-ui/tree/v5.14.4/docs/data/material/getting-started/templates/sign-in **/ import { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { auth, logInWithEmailAndPassword } from '../firebase'; import { useAuthState } from 'react-firebase-hooks/auth'; import { createTheme, ThemeProvider } from '@mui/material/styles'; import Avatar from '@mui/material/Avatar'; import Button from '@mui/material/Button'; import CssBaseline from '@mui/material/CssBaseline'; import TextField from '@mui/material/TextField'; import Paper from '@mui/material/Paper'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; import Typography from '@mui/material/Typography'; function Copyright(props: any) { return ( <Typography variant="body2" color="text.secondary" align="center" {...props} > {'Copyright © '} Etienne Rivard {new Date().getFullYear()} {'.'} </Typography> ); } const defaultTheme = createTheme(); function Login() { const [user, loading] = useAuthState(auth); const navigate = useNavigate(); useEffect(() => { if (loading) { // maybe trigger a loading screen return; } if (user) navigate('/'); // eslint-disable-next-line react-hooks/exhaustive-deps }, [user, loading]); const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); const data = new FormData(event.currentTarget); logInWithEmailAndPassword( data.get('email') as string, data.get('password') as string ); }; return ( <ThemeProvider theme={defaultTheme}> <Grid container component="main" sx={{ height: '87vh', width: '90vw' }}> <CssBaseline /> <Grid item xs={false} sm={4} md={7} sx={{ backgroundImage: 'url(https://source.unsplash.com/random?wallpapers)', backgroundRepeat: 'no-repeat', backgroundColor: (t) => t.palette.mode === 'light' ? t.palette.grey[50] : t.palette.grey[900], backgroundSize: 'cover', backgroundPosition: 'center', }} /> <Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square> <Box sx={{ my: 8, mx: 4, display: 'flex', flexDirection: 'column', alignItems: 'center', }} > <Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}> <LockOutlinedIcon /> </Avatar> <Typography component="h1" variant="h5"> "S'authentifier à X" </Typography> <Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 1 }} > <TextField margin="normal" required fullWidth id="email" label="courriel" name="email" autoComplete="email" autoFocus /> <TextField margin="normal" required fullWidth name="password" label="mot de passe" type="password" id="password" autoComplete="current-password" /> <Button type="submit" fullWidth variant="contained" sx={{ mt: 3, mb: 2 }} > S'authentifier </Button> <Copyright sx={{ mt: 5 }} /> </Box> </Box> </Grid> </Grid> </ThemeProvider> ); } export default Login;
-
Rediriger vers la page de login si l'utilisateur n'est pas authentifié :
routes/home.route.tsximport { Home } from '../components/home.component'; import { useEffect } from 'react'; import { useAuthState } from 'react-firebase-hooks/auth'; import { useNavigate } from 'react-router-dom'; import { auth } from '../firebase'; export const HomeRoute = () => { const [user, loading] = useAuthState(auth); const navigate = useNavigate(); useEffect(() => { // si loading = true, ça veut dire que le firebase n'est pas encore prêt. if (loading) return; // si user est null, l'utilisateur n'est pas authentifié if (!user) navigate('/login'); // eslint-disable-next-line react-hooks/exhaustive-deps }, [user, loading]); return <Home />; };
CodeSandbox
Installation dans un API
Firebase peut créer des jetons dans l'application React qui servent à valider l'utilisateur dans votre API.
-
Installer le module Firebase Admin à votre API :
-
Générer firebase.json à partir de la console Firebase :
Le fichier généré donne un accès administratif à votre projet. Ne pas le mettre dans votre github!
-
Coder un intergiciel pour protéger vos routes :
authentificationFirebase.tsimport admin from 'firebase-admin'; import express, { Express, Request, Response, NextFunction } from 'express'; var serviceAccount = require('../firebase.json'); admin.initializeApp({ credential: admin.credential.cert(serviceAccount), }); export const firebaseAuthentication = async ( req: Request, res: Response, next: NextFunction ) => { const authHeader = req.headers.authorization; console.log('start firebaseAuthentication'); if (authHeader) { const idToken = authHeader.split(' ')[1]; console.log('idToken:', idToken); admin .auth() .verifyIdToken(idToken) .then(function (decodedToken) { console.log('Next()'); next(); }) .catch(function (error) { console.log('catch Error:', error); const errorMessage = { status: 403, error: error, }; res.sendStatus(403).send(errorMessage); res.end(); }); } else { console.log('no header'); const errorMessage = { status: 401, error: 'Missing authorization header', }; res.sendStatus(401); res.end(); } };
-
Dans votre application React, générer le jeton :
-
Dans votre application React, envoyer le jeton dans l'appel de votre API :
CodeSandbox