Protéger son application

Publié le 13 mai 2017
#react# firebase# security

Attention, cet article n'est pas à jour.

Lorsque l’on code une application React, on est vite amené à protéger l’accès à une partie de celle-ci. Je vais vous montrer ici ma technique préférée afin de filtrer l'accès à certains composants.

Cette technique est applicable autant pour React que pour React-Native, mais pour les besoins de l’article, je prendrai seulement exemple sur une application React.

Principe général

Afin d’interdire l’accès à certain composant si l’utilisateur n’est pas authentifié ou n’a pas les bons droits, nous allons englober ceux-ci dans un super-composant. Il n’aura qu’un seul but :

  • vérifier un certain nombre de conditions et agir en conséquence.

Si les conditions requises sont réunies, le composant affichera ce qui est demandé, sinon il redirigera l’utilisateur.

Protéger son Application

Application de base

Prenons une simple application web avec trois routes différentes

  • Accueil
  • Login
  • Admin

et avec le code ci-dessous (extrait) :

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom';

import AppComponent from './components/app.component';
import HomeComponent from './components/home.component';
import AdminComponent from './components/admin.component';
import LoginComponent from './components/login.component';

import * as firebase from 'firebase';
import { config } from './vars/firebase.config';
import './assets/css/index.css';

firebase.initializeApp(config);

ReactDOM.render(
  <Router>
    <AppComponent>
      <Route exact path="/" component={HomeComponent} />
      <Route path="/admin" component={AdminComponent} />
      <Route path="/login" component={LoginComponent} />
    </AppComponent>
  </Router>,
  document.getElementById('root'),
);

Pour voir l’application que j’ai codée pour cet article, c’est par ici : https://github.com/gkueny/protect-route-react J’utilise Firebase et React Router v4.

/!\ Pour l’instant, notre composant AdminComponent est en libre accès /!\.

Ce que l’on cherche donc à faire, c’est de vérifier le token de l’utilisateur (qu’il en est un et que le serveur nous confirme son authenticité) et d’agir en conséquence.

Notre super-composant

Une solution rapide, serait d’effectuer nos vérifications directement dans le composant AdminComponent. Cela marcherai, mais on serait amené à recopier le code pour chaque composant à protéger. Pas très efficace comme méthode et chronophage 🙁 .

Au lieu de cela, nous allons construire un composant qui englobera les composants à protéger et fera ces vérifications pour nous :

import React, { Component } from 'react';
import PropTypes from 'prop-types';

import { Route, Redirect } from 'react-router-dom';
import { checkUserStatus } from '../services/user.service';

class ProtectedRoute extends Component {
  constructor() {
    super();
    this.state = {
      isLogIn: null,
    };
  }

  componentDidMount() {
    //On check le statut de l'utilisateur auprès du serveur, et on agit en conséquance
    checkUserStatus().then(this.updateUserStatus);
  }

  //Sera appelé lorsque l'on changera de route
  componentWillReceiveProps(nextProps) {
    this.state = {
      isLogIn: null,
    };
    //On fait également cette vérification lors d'un changement
    checkUserStatus().then(this.updateUserStatus);
  }

  updateUserStatus = isLogIn => {
    if (this.state.isLogIn !== isLogIn) {
      this.setState({
        isLogIn,
      });
    }
  };

  render() {
    const { component: Component, ...rest } = this.props;

    return (
      <Route
        {...rest}
        render={props => {
          if (this.state.isLogIn) {
            return <Component {...props} />;
          } else if (this.state.isLogIn !== null) {
            return <Redirect to={'login'} />;
          } else {
            return null;
          }
        }}
      />
    );
  }
}

ProtectedRoute.propTypes = {
  component: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
  location: PropTypes.object,
};

export default ProtectedRoute;

Ici, à chaque fois qu’une des routes géré par ProtectedRoute est appelée, la fonction componentWillReceiveProps() sera exécutée, car le composant Router définit dans index.js « re-rend » notre composant lors d’un changement de route.

On profite donc de cette fonction pour vérifier les droits de l’utilisateur et afficher ou non la route demandée.

Intégration du super-composant

Pour intégrer notre super-composant, il nous suffit de modifier la fonction render() de notre fichier index.js :

<Router>
  <AppComponent>
    <Route exact path="/" component={HomeComponent} />
    <Route path="/login" component={LoginComponent} />
    <ProtectedRoute path="/admin" component={AdminComponent} />
  </AppComponent>
</Router>
Et c'est tout !

On a donc juste eu besoin de définir une surcouche du composant Route pour protéger certaines d’entre elle. Avec ce code la lecture du code est assez intuitif et nous n’aurons pas besoin de faire de copier-coller pour chaque composant privé.

gkueny

À propos de l'auteur - gkueny @ZEBet

Développeur depuis maintenant 6 ans, j'ai une grande affinité avec le mobile et les tests bien fait. Pas full-stack mais touche à tout, je suis également à l'aise sur du Symfony / php.