Est-ce que vous êtes entrain d’essayer d’ajouter un petit système d’authentification ReactJS dans votre application, sans succès ? J’ai également été dans cette situation il n’y a pas si longtemps, et ça a vraiment été la croix et la bannière pour mettre en place une fonctionnalité qui est pourtant… indispensable.

Pour le moment, votre application n’est pas très fiable en termes de sécurité. N’importe quel visiteur peut naviguer entre vos pages, ou modifier des données dans votre application. Or ce n’est pas forcément ce que vous voulez !

Plus grave encore, tant que l’on a pas un système d’authentification, on ne peut pas créer un espace membre dans son application, ni aucune interaction avec ses futurs utilisateurs. À ce moment-là, je me suis dit autant créer un site avec WordPress…

Heureusement, nous pouvons bien sûr développer notre propre système d’authentification ReactJS. Comme React JS en tant que tel ne s’occupe pas de l’authentification, mais seulement de l’interaction entre vos composants et le DOM virtuel, nous allons devoir faire un peu de développement de notre côté.

Pour mettre en place une authentification dans React JS, nous allons avoir besoin de créer un nouveau composant nommé PrivateRoute, afin de sécuriser certaines des routes de notre application. En avant !

1. Mettre en place l’authentification ReactJS

Les applications ont souvent besoin de restreindre l’accès à certaines fonctionnalités, en fonction de l’utilisateur. Par exemple, obliger l’utilisateur à s’authentifier pour accéder à son espace membre. Il faut alors bloquer ou limiter l’accès jusqu’à ce que l’utilisateur soit connecté.

Afin d’illustrer la mise en place de l’authentification, nous imaginer que votre application nécessite une connexion de la part de l’utilisateur, comme ceci :

Formulaire de connexion pour mettre en place l'authentification ReactJS.
Il faut protéger notre espace membre avec un formulaire de connexion !

Mais pour cela, vous devez commencer par créer un service qui permettra à l’utilisateur de se connecter, et indiquer au reste de l’application si l’utilisateur est connecté ou non. L’idéal serait que vous créez un nouveau service dédié à l’authentification dans votre application, que vous pourriez nommé authentication-service.ts :

export default class AuthenticationService {
 
  static isAuthenticated: boolean = false;
 
  static login(username: string, password: string): Promise<boolean> {
    const isAuthenticated = (username === 'admin' &amp;&amp; password === 'motdepasse');
 
    return new Promise(resolve => {
      setTimeout(() => {
        this.isAuthenticated = isAuthenticated;
        resolve(isAuthenticated);
      }, 1000);
    });
  }
}

Alors que l’on pourrait s’attendre à un service très complexe ici, on peut s’étonner qu’il soit aussi court. C’est parce qu’il s’agit d’une version minimum de l’authentification ReactJS, afin de mieux comprendre comment cela fonctionne. Libre à vous par la suite d’améliorer ce système, d’ailleurs je vous y encourage fortement. 😉

Mais revenons à nos explications. Ce service met à disposition plusieurs propriétés et méthodes :

  • La propriété isAuthenticated (ligne 3) : C’est un booléen qui permet de savoir si l’utilisateur courant est connecté ou non. Par défaut, l’utilisateur est déconnecté lorsque l’application démarre. Heureusement ! 😅
  • La méthode login (ligne 5) : Cette méthode simule une connexion à une API Rest externe en retournant une promesse qui se résout avec succès si l’utilisateur a entré les identifiants « admin/motdepasse », après un délai de 1000 millisecondes, soit 1 seconde. Bien sûr, vous pouvez personnaliser cette méthode pour correspondre à vos propres besoin de connexion par la suite. Si l’utilisateur s’est connecté correctement, alors notre méthode retourne true. Sinon, on retourne false et tant pis pour notre visiteur.

On a maintenant un service d’authentification prêt à être utiliser. 👍

Cependant, avant de lancer l’application, nous devons encore faire deux choses :

  • Il nous reste encore à créer une page avec un formulaire de connexion, et à le brancher sur ce service.
  • Sécuriser les routes protégées de notre application. En effet, si l’utilisateur rentre l’url « /admin » à la main, il faut qu’il soit rediriger vers le formulaire de connexion s’il n’est pas authentifié.

2. Ajouter un formulaire de connexion

Nous devons créer un formulaire pour la page de connexion, pour permettre aux utilisateurs autorisés d’accéder à notre application. Le formulaire de connexion présent sur cette page s’assurera de vérifier les identifiants saisis par l’utilisateur, et redirigera les « bons » utilisateurs sur votre espace membre.

Créez donc un nouveau composant nommé login.tsx. Je vous préviens, la quantité de code peut vous faire peur, pourtant il s’agit d’un simple formulaire de connexion avec deux champs username et password. Ne vous laissez pas impressionner donc, il n’y a pas de raisons de paniquer ! 😉

import React, { FunctionComponent, useState } from 'react';
import { useHistory } from 'react-router-dom';
import AuthenticationService from '../services/authentication-service';
 
type Field = {
  value?: any,
  error?: string,
  isValid?: boolean
};
 
type Form = {
  username: Field,
  password: Field
}
 
const Login: FunctionComponent = () => {
 
  const history = useHistory();
 
  const [form, setForm] = useState<Form>({
    username: { value: '' },
    password: { value: '' },
  });
 
  const [message, setMessage] = useState<string>('Vous êtes déconnecté.');
 
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    const fieldName: string = e.target.name;
    const fieldValue: string = e.target.value;
    const newField: Field = { [fieldName]: { value: fieldValue } };
 
    setForm({ ...form, ...newField});
  }
 
  const validateForm = () => {
    let newForm: Form = form;
 
    // Validator username
    if(form.username.value.length < 3) {
      const errorMsg: string = 'Votre prénom doit faire au moins 3 caractères de long.';
      const newField: Field = { value: form.username.value, error: errorMsg, isValid: false };
      newForm = { ...newForm, ...{ username: newField } };
    } else {
      const newField: Field = { value: form.username.value, error: '', isValid: true };
      newForm = { ...newForm, ...{ username: newField } };
    }
 
    // Validator password
    if(form.password.value.length < 6) {
      const errorMsg: string = 'Votre mot de passe doit faire au moins 6 caractères de long.';
      const newField: Field = {value: form.password.value, error: errorMsg, isValid: false};
      newForm = { ...newForm, ...{ password: newField } };
    } else {
      const newField: Field = { value: form.password.value, error: '', isValid: true };
      newForm = { ...newForm, ...{ password: newField } };
    }
 
    setForm(newForm);
 
    return newForm.username.isValid &amp;&amp; newForm.password.isValid;
  }
 
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const isFormValid = validateForm();
    if(isFormValid) {
      setMessage('👉 Tentative de connexion en cours ...');
      AuthenticationService.login(form.username.value, form.password.value).then(isAuthenticated => {
        if(!isAuthenticated) {
          setMessage('🔐 Identifiant ou mot de passe incorrect.');
          return;
        }
         
        history.push('/admin'); // L'utilisateur est bien connecté !
         
      });
    }
  }
 
  return (
    <form onSubmit={(e) => handleSubmit(e)}>
      <div className="row">
        <div className="col s12 m8 offset-m2">
          <div className="card hoverable">
            <div className="card-stacked">
              <div className="card-content">
                {/* Form message */}
                {message &amp;&amp; <div className="form-group">
                  <div className="card-panel grey lighten-5">
                    {message}
                  </div>
                </div>}
                {/* Field username */}
                <div className="form-group">
                  <label htmlFor="username">Identifiant</label>
                  <input id="username" type="text" name="username" className="form-control" value={form.username.value} onChange={e => handleInputChange(e)}></input>
                  {/* error */}
                  {form.username.error &amp;&amp;
                  <div className="card-panel red accent-1"> 
                   {form.username.error} 
                  </div>} 
                </div>
                {/* Field password */}
                <div className="form-group">
                  <label htmlFor="password">Mot de passe</label>
                  <input id="password" type="password" name="password" className="form-control" value={form.password.value} onChange={e => handleInputChange(e)}></input>
                  {/* error */}
                  {form.password.error &amp;&amp;
                  <div className="card-panel red accent-1"> 
                   {form.password.error} 
                  </div>} 
                </div>
              </div>
              <div className="card-action center">
                {/* Submit button */}
                <button type="submit" className="btn">Valider</button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </form>
  );
};
  
export default Login;

Bien que le code de ce composant semble interminable, je vous assure qu’il s’agit simplement d’une page affichant un formulaire de connexion. Mais voici tout de même quelques explications pour vous présenter comment je vois les choses :

  • De la ligne 20 à 23, on définit un état pour notre formulaire, pour sauvegarder les données saisies par l’utilisateur.
  • À la ligne 25, on sauvegarde dans un état un message d’information global pour le formulaire. Par exemple: « Tentative de connexion en cours », « Identifiant ou mot de passe incorrects », etc.
  • À la ligne 35, on définit une méthode pour valider les champs de notre formulaire. Pour le champ username, on vérifie simplement qu’il fait plus de 3 caractère de long, à la ligne 39. Et pour le mot de passe password, on vérifie qu’il fait au moins 6 caractères de long, à la ligne 49.
  • À la ligne 63, on gère la soumission de notre formulaire. Tout d’abord à la ligne 67, on affiche un message du type « Authentification en cours », car le délai que nous avons définis précédemment pour l’authentification d’un utilisateur est de 1000 millisecondes. Ensuite, à la ligne 69, on gère le cas ou les identifiants de l’utilisateurs sont incorrects, et on affiche le message d’erreur correspondant « Identifiant ou mot de passe incorrect ». Puis à la ligne 71, on gère le cas où l’utilisateur s’est connecté correctement. Vous pouvez alors le redirigé vers votre espace membre, car tout s’est bien passé.
  • Enfin, même si le template de composant est classique pour un formulaire, vous remarquez qu’on affiche notre message d’information du formulaire de la ligne 88 à 92, afin de prévenir l’utilisateur des traitements en cours.

Après s’être authentifié correctement, l’utilisateur sera redirigé vers la liste des pokémons. C’est parfait ! 👍

Pour terminer, il faut bien sûr ajouter votre nouvelle page de connexion login auprès du routeur de React, pour qu’il soit au courant de cette nouvelle route.

Ajoutez donc notre nouvelle route de connexion dans le composant racine App.tsx :

// Les autres importations.
import Login from './pages/login';
  
const App: FunctionComponent = () => {
  
  return (
    <Router>
      ...
      <Switch>
        ... Les autres routes de votre application ...
        <Route exact path="/login" component={Login} />
      </Switch>
      </div>
    </Router>
  );
}
  
export default App;

Maintenant, essayer d’accéder à votre application.

Et il ne se passera rien, tant que vous ne vous rendez pas vous-même sur la route « /login » de votre application … 🤔

En fait, c’est normal, car par défaut notre application se rend sur l’url « / », n’est pas protégée. Donc que l’utilisateur soit connecté ou non ne change rien au fonctionnement de notre application. Pour accéder à notre formulaire de connexion, il faut saisir manuellement la route « /login » dans votre navigateur. Ce n’est pas vraiment ce que nous voulons.

Pour résumé, on a un service d’authentification fonctionnel, un formulaire de connexion qui tourne plutôt bien, mais… les autres routes de notre application ne sont pas encore protégées. Encore un peut de boulot donc !

Si vous n’avez jamais utilisé le router de React, ou que vos application React JS n’avait toujours qu’une seule page, je ne peux que vous recommandé de regarder sur Internet « React router« . Il s’agit d’une petite librairie vous permettant de mettre en place la navigation dans votre application. Sinon, j’ai également publié un autre article au sujet du router de React JS sur ce blog, que vous pouvez aller consulter librement. Mais revenons à notre problème !

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

L’ensemble des routes de notre application est accessible à tout le monde pour le moment. 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 la page demandée par l’utilisateur, 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 dans votre projet :

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 la page initialement demandée, comme la page d’acceuil de votre espace membre, ou la partie administration de votre site.

Attend, mais il y a plein de trucs bizarres !  🧐

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.

Ensuite, pour sécuriser les routes de votre application, retournez dans le composant App.tsx où sont normalement déclarées les routes de votre application :

// Les autres importations.
import PrivateRoute from './PrivateRoute';
  
const App: FunctionComponent = () => {
  
  return (
    <Router>
      ...
      <Switch>
        ... Les autres routes de votre application ...
        <Route exact path="/login" component={Login} /> // Route publique
        <PrivateRoute exact path="/admin" component={Admin} /> // Route protégée
      </Switch>
      </div>
    </Router>
  );
}
  
export default App;

On a maintenant des Route et des PrivateRoute, aux lignes 10 et 11, dont l’accès aux PrivateRoute restreint aux utilisateurs connectés. D’ailleurs, simplement en relisant notre code on comprend bien le fonctionnement de la navigation dans notre application !

Bilan

Nous savons désormais comment mettre en place un système d’authentification dans notre application. Cela a été possible grâce à l’utilisation d’un service, d’un formulaire de connexion, et d’un composant maison nommé PrivateRoute. Notre système est assez minimal pour l’instant, l’idée était de mettre les pieds dans le plat et de comprendre le fonctionnement de l’authentification avec React.

Maintenant, vous devriez avoir plus confiance en vous, et votre espace membre ne sera plus accessible à n’importe quel visiteur ! Félicitations, après avoir tapé autant de lignes de code, nous avons enfin être fier de nous. 😎

En tous cas, j’espère que ce modeste article vous aura aidé à mettre en place l’authentification dans votre application React JS !


Si vous avez besoin de consolider vos connaissances sur React JS pour votre travail, vos études ou vos projets personnels, je vous invite à me rejoindre pour une semaine complète avec React JS. Nous apprendrons React JS de zéro, en se basant sur les connaissances indispensables qu’ils manquent dans beaucoup de tutoriels : Les Composants Web, TypeScript ou encore ECMAScript 6.

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.