Aller au contenu

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.

L'application dorsale utilisée dans l'exemple suivant est une application Express avec une route pour générer un jeton et une route pour la liste des utilisateurs.

Commençons par le contexte de l'application React :

/src/contexts/LoginContext.tsx
import axios from 'axios';
import { createContext, useState } from 'react';

export type LoginContextType = {
  isLoggedIn: boolean;
  token: string;
  login: (email: string, password: string) => Promise<boolean>;
  logout: () => void;
};

export const LoginContext = createContext<LoginContextType>({
  isLoggedIn: false,
  token: '',
  login: () => new Promise<boolean>(() => false),
  logout: () => {},
});

export default function LoginProvider(props: any) {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [token, setToken] = useState('');

  async function login(email: string, password: string) {
    return axios
      .post('http://localhost:3001/api/users/generatetoken', {
        email,
        password,
      })
      .then((response) => {
        const { token } = response.data;
        if (token) {
          setIsLoggedIn(true);
          setToken(token);
          return true;
        } else {
          setIsLoggedIn(false);
          setToken('');
          return false;
        }
      });
  }

  function logout() {
    setToken('');
    setIsLoggedIn(false);
  }

  const values = { isLoggedIn, token, login, logout };

  return (
    <LoginContext.Provider value={values}>
      {props.children}
    </LoginContext.Provider>
  );
}

Le contexte se charge de faire l'appel à l'API pour authentifier l'utilisateur avec son courriel et son mot de passe et à conserver le jeton qui sera utilisé plus tard.

Allons voir la page de login :

/src/components/Login/Login.tsx
import { useContext, useEffect, useState } from 'react';
import { LoginContext } from '../../contexts/LoginContext';
import { useNavigate } from 'react-router-dom';

function Login() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [erreur, setErreur] = useState('');
  const navigate = useNavigate();
  const { login, isLoggedIn } = useContext(LoginContext);

  async function performLogin() {
    await login(email, password)
      .then((reussi) => {
        if (reussi) {
          setErreur('');
        }
      })
      .catch(() => setErreur('Login incorrect'));
  }

  useEffect(() => {
    if (isLoggedIn) {
      navigate('/');
    }
  }, [isLoggedIn]);

  return (
    <div className="bg-gray-100 flex items-center justify-center h-screen">
      <div className="flex flex-col md:flex-row bg-white rounded-2xl shadow-2xl max-w-4xl">
        <div className="md:w-1/2 p-12 flex items-center justify-center">
          <img
            src="/catlogin.png"
            alt="Le chat valide ton login"
            className="max-w-xs md:max-w-sm"
          />
        </div>

        <div className="md:w-1/2 p-12">
          <h2 className="text-3xl font-bold text-gray-800 mb-4">Bienvenue !</h2>
          <p className="text-gray-600 mb-8">Connectez-vous pour continuer.</p>

          <form action="#" method="POST">
            <div className="mb-4">
              <label className="block text-gray-700 text-sm font-bold mb-2">
                Adresse e-mail
              </label>
              <input
                type="email"
                id="email"
                name="email"
                onChange={(e) => setEmail(e.target.value)}
                className="shadow-sm appearance-none border rounded-lg w-full py-3 px-4 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-purple-400 focus:border-transparent transition duration-300 ease-in-out"
              />
            </div>

            <div className="mb-6">
              <label className="block text-gray-700 text-sm font-bold mb-2">
                Mot de passe
              </label>
              <input
                type="password"
                id="password"
                name="password"
                placeholder="******************"
                onChange={(e) => setPassword(e.target.value)}
                className="shadow-sm appearance-none border rounded-lg w-full py-3 px-4 text-gray-700 mb-3 leading-tight focus:outline-none focus:ring-2 focus:ring-purple-400 focus:border-transparent transition duration-300 ease-in-out"
              />
            </div>

            <div className="mb-6">
              <p className="block text-red-600">{erreur}</p>
            </div>

            <div className="flex items-center justify-between">
              <button
                type="button"
                onClick={() => performLogin()}
                className="bg-purple-500 hover:bg-purple-700 text-white font-bold py-3 px-6 rounded-lg focus:outline-none focus:shadow-outline transition duration-300 ease-in-out transform hover:-translate-y-1"
              >
                Se connecter
              </button>
            </div>
          </form>
        </div>
      </div>
    </div>
  );
}

export default Login;

Bâtissons le reste de l'application :

/src/components/Menu/Menu.tsx
import { useContext, useEffect, useState } from 'react';
import { Outlet, useNavigate } from 'react-router-dom';
import { LoginContext } from '../../contexts/LoginContext';

function Menu() {
  const { isLoggedIn, logout } = useContext(LoginContext);

  const navigate = useNavigate();
  useEffect(() => {
    if (!isLoggedIn) {
      navigate('/login');
    }
  }, [isLoggedIn]);

  return (
    <>
      <nav className="bg-gray-800 p-4">
        <div className="container mx-auto flex justify-start items-center">
          <ul className="flex space-x-4">
            <li>
              <a href="/" className="text-white hover:text-gray-300">
                Liste des utilisateurs
              </a>
            </li>
          </ul>
          <div className="w-250"></div>
          <button
            className="text-white hover:text-gray-300"
            onClick={() => logout()}
          >
            Se déconnecter
          </button>
        </div>
      </nav>
      <Outlet />
    </>
  );
}

export default Menu;
/src/components/UserList/UserList.tsx
import { useContext, useEffect, useState } from 'react';
import { LoginContext } from '../../contexts/LoginContext';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';

interface IUser {
  name: string;
  email: string;
  password: string;
}

function UserList() {
  const listeVide: IUser[] = [];
  const { isLoggedIn, token } = useContext(LoginContext);
  const [userList, setUserList] = useState(listeVide);

  const navigate = useNavigate();
  useEffect(() => {
    if (!isLoggedIn) {
      navigate('/login');
    } else {
      axios
        .get('http://localhost:3001/api/users/all', {
          headers: {
            Authorization: `Bearer ${token}`,
          },
        })
        .then((response) => setUserList(response.data.users));
    }
  }, [isLoggedIn]);

  return (
    <>
      {userList &&
        userList.map((user) => {
          return (
            <div
              key={user.name}
              className="max-w-sm mx-auto bg-white shadow-lg rounded-lg overflow-hidden"
            >
              <div className="px-6 py-4">
                <div className="font-bold text-xl mb-2">{user.name}</div>
                <p className="text-gray-700 text-base">{user.email}</p>
                <p className="text-gray-700 text-base">{user.password}</p>
              </div>
            </div>
          );
        })}
    </>
  );
}

export default UserList;

Notez bien la manière de faire un get avec le jeton :

        axios.get('http://localhost:3001/api/users/all', {
          headers: {
            Authorization: `Bearer ${token}`,
          },
        })
        .then((response) => setUserList(response.data.users));
/src/App.tsx
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Login from './components/Login';
import LoginProvider from './contexts/LoginContext';
import UserList from './components/UserList';
import Menu from './components/Menu';

function App() {
  return (
    <>
      <LoginProvider>
        <BrowserRouter>
          <Routes>
            <Route path="/" element={<Menu />}>
              <Route index element={<UserList />} />
            </Route>
            <Route path="/login" element={<Login />} />
          </Routes>
        </BrowserRouter>
      </LoginProvider>
    </>
  );
}

export default App;