3. Protéger l’accès aux routes

L’ensemble des routes de notre application est accessible à tout le monde pour le moment. Si nous entrons l’url « /pokemons » dans notre navigateur, nous serons redirigés vers la liste des pokémons, sans même s’être authentifié depuis le formulaire de connexion. On a déjà vu mieux pour sécuriser une application ! 🤭

Nous allons donc nous faire aider par un nouveau composant nommé PrivateRoute. Ce composant repose sur un mécanisme relativement simple, soit il retourne le composant demandé : <PokemonList>, <PokemonDetail>, etc… sinon il redirige l’utilisateur vers la page de connexion. Bien sûr, ce choix n’est pas arbitraire, et se fera en fonction des informations de connexion saisies par l’utilisateur.

Passons tout de suite à la pratique, et regardons concrètement comment ce composant PrivateRoute va fonctionner. Créer donc un nouveau composant PrivateRoute.tsx à la racine de votre projet, car ce composant n’est ni une page en soit, ni un composant en tant que telle (on ne vas pas l’inclure dans d’autres pages) :

import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import AuthenticationService from './services/authentication-service';

const PrivateRoute = ({ component: Component, ...rest }: any) => (
  <Route {...rest} render={(props) => {
    const isAuthenticated = AuthenticationService.isAuthenticated;
    if (!isAuthenticated) {    
      return <Redirect to={{ pathname: '/login' }} />
    }

    return <Component {...props} />
  }} />
);

export default PrivateRoute;

Le code peut paraître bizarre, et il l’est par rapport à ce que nous avons déjà vu. Commençons par les grands principes avant de voir les d’autres précisions :

  • À la ligne 7, on demande à notre service s’il y a actuellement un utilisateur connecté dans notre application.
  • À la ligne 9, on redirige l’utilisateur vers la page de connexion s’il ne s’est pas connecté.
  • À la ligne 11, on redirige l’utilisateur vers le composant demandé, comme la liste des pokémons par exemple.

Attend, mais il y a plein de trucs bizarres ! C’est quoi {…rest} ? Pourquoi on fait une redirection en retournant un objet <Redirect /> ? C’est quoi <Component {…props} /> ?? 🧐

Vous avez tout à fait raison, il y a bien une petite particularité derrière tout ça. En fait, notre composant PrivateRoute est une surcouche, fait maison, de l’élément Route du router de React. D’ailleurs à la ligne 6, vous voyez que notre composant n’est qu’un élément Route, et qu’on personnalise simplement son attribut render, c’est-à-dire le comportement de rendu de cette route.

Pour comprendre tout ça, retournez dans le composant App.tsx, et reprenez la route de la liste des pokémons :

<!-- Cette route n'est pas sécurisé... -->
<Route exact path="/" component={PokemonsList} />

Pour sécuriser cette route, il faudrait faire ceci :

<!-- Cette route est sécurisé... -->
<PrivateRoute exact path="/" component={PokemonsList} />

Que s’est-il passer entre ces deux codes ? Et bien PrivateRoute va récupérer en entrée la propriété component, c’est-à-dire le composant en tant que tel, et tous les props associés, à savoir exact et path :

const PrivateRoute = ({ component: Component, ...rest }: any) => (...);

Dans ce cas, component vaut PokemonList, et …rest vaut : {exact: true, path: « / »}. Vous voyez le lien entre toutes ces extraits de code ?

Ensuite, pour le routeur de React, on va lui retourner une route vers le composant demandé. Dans notre cas, il s’agit de la liste des pokémons :

return <Component {...props} />;

Sinon, si l’utilisateur n’est pas connecté, on lui retourne une redirection vers le formulaire de connexion :

return <Redirect to={{ pathname: '/login' }} />

Au final, une fois que le moteur JSX a interprété notre élément PrivateRoute, on obtiens quelque chose comme ça :

<PrivateRoute exact path="/" component={PokemonsList} /> // Nous, on écrit ça dans notre code. 

// Une fois que JSX a interprété notre code :
<Switch>
 <Component {...props} /> // Équivaut à une redirection vers le composant "PokemonList" dans notre exemple.
 // OU
 <Redirect to={{ pathname: '/login' }} /> // Redirection vers le formulaire de connexion si l'utilisateur n'est pas connecté.
</Switch>

Voilà, j’espère que mes explications sont assez claires. 😇

Prenez le temps de relire le code si nécessaire, et même si vous ne comprenez pas chaque détail de cette syntaxe un peu verbeuse, assurez-vous d’avoir compris le grand principe que je viens de vous présenter.

Enfin, il faut utiliser ce nouveau composant PrivateRoute à la place de l’élément Route que nous utilisions précédemment auprès du router de React. Retournez donc dans le composant racine App.tsx, afin de mettre à jour le fonctionnement de notre système de navigation :

// Les autres importations.
import PrivateRoute from './PrivateRoute';
 
const App: FunctionComponent = () => {
 
  return (
    <Router>
      ...
      <Switch>
        <PrivateRoute exact path="/" component={PokemonsList} />
        <Route exact path="/login" component={Login} />
        <PrivateRoute exact path="/pokemons" component={PokemonsList} />
        <PrivateRoute exact path="/pokemon/add" component={PokemonAdd} />
        <PrivateRoute exact path="/pokemons/edit/:id" component={PokemonEdit} />
        <PrivateRoute path="/pokemons/:id" component={PokemonsDetail} />
        <Route component={PageNotFound} />
      </Switch>
      </div>
    </Router>
  );
}
 
export default App;

On a maintenant des Route et des PrivateRoute, dont l’accès est restreint aux utilisateurs connectés. D’ailleurs, simplement en relisant notre code on comprend bien le fonctionnement de la navigation dans notre application. Si vous retournez dans votre application, vous serez automatiquement redirigé vers le formulaire de connexion, car au démarrage de l’application vous n’avez pas encore le droit d’accéder à la liste des pokémons !

Donc tout fonctionne correctement désormais. De plus, le code est concis, efficace et plutôt simple à lire. C’est du code de qualité, félicitations ! 👊