090 - Redux Chili Peppers
On y est ! les Redux sont là !

Maintenant que tu peux t'authentifier auprès du serveur, tu vas devoir gérer la session coté client dans React. Pour cela tu vas devoir gérer ton token :
- le garder dans ta session afin de naviguer dans ton application correctement
- l'insérer dans l'entête de chacune de requête auprès du serveur
Objectifs
- Avoir une application React qui communique avec le serveur de façon authentifié !
Récupération du JWT
Afin d'être synchro, dans le fichier SiginIn.jsx tu dois avoir une fonction de soumission de formulaire ressemblant à :
handleSubmit=(e)=> {
e.preventDefault();
fetch("/auth/signin", {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json'
}),
body: JSON.stringify(this.state),
})
.then(res => {
if (res.ok)
return res.json()
else
throw new Error(res.statusText);
})
.then(res => this.setState({ "flash": res.message }))
.catch(err => this.setState({ "flash": err.message }))
}
Vérifie et modifie ton backend pour que message soit bien remonté. Normalement tu dois avoir la snackbar qui te notifie si tu es authentifié ou non.
Si tu loggues le résultat du fetch dans ta console tu verras que l'objet obtenu du serveur ressemble à :
{
"user": {
"email": "homer@wildcodeschool.fr"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRvdG9AdGF0YS5mciIsImlhdCI6MTUyODQ0NDYxMX0.c0L_l0in5O1FNGRCgT-Gv6Rn6wX_bRBZTDk9VS_p5LE",
"message": "User sign in!"
}
Le serveur retourne des informations sur l'utilisateur authentifié, un token JWT, et un message.
Cool, tu as récupéré le token !
JWT et Redux
Le jwt est l'identifiant de l'utilisateur. Chacune des requêtes au serveur doit 'porter' - bearer - ce jeton pour que le serveur comprenne quel utilisateur est derrière celle-ci.
On comprend bien que l'information va être utiliser à plusieurs endroit dans l'application React : au niveau des accès de page, au niveau de la création des sessions, au niveau des générations des requêtes AJAX... Le token va devoir être stoker dans un store global à l'application. Ainsi l'information pourra être accessible de n'importe où.
C'est ici que Redux rentre en scène et ca va envoyer du lourd !
Redux est une librairie qui va permettre de gérer des états globaux de l'application qui vont être stockés dans un store. Nous allons parler de :
- reducer : qui va, à partir de l'ancien état et de nouvelles données, établir un nouvel état
- store : états issus de l'ensemble des reducers
- container : composant React qui est connecté à Redux
- actions : que nous verrons plus tard
Tout d'abord du doit installer Redux :
npm install --save redux react-redux
On va rentrer dans le détail de Redux dans les prochaines étapes.
Redux - Mise en place de l'environnement
Avant d'aller plus loin, il est nécessaire de préparer l'environnement Redux. En l'occurence modifier l'arborescence du projet. Celle-ci est assez standard dans l'univers Redux... il est préférable de la respecter.
Crée les dossiers afin de suivre l'arborescence ci-dessous.
On distingue les components des containers car tous les composants React ne seront pas connectés à Redux et donc ne seront pas des conteneurs. Dans notre cas les trois composants vont devenir des conteneurs par la suite donc tu peux déplacer les fichiers jsx dans containers comme indiqué.
Attention il va falloir revoir les import pour que tout le monde se retrouve.
/src
/actions
/components
/containers
SignIn.jsx
SignUp.jsx
Profile.jsx
/reducers
App.js
App.css
index.js
index.css
La fonction Redux Connect où comment devenir container
Maintenant, reprend ton composant SignIn où tu récupères ton token.
L'idée est de stocker le jeton dans Redux. Pour cela nous allons transformer le component en container !
Redux propose la fonction connect pour cela. Tout simplement on remplace :
export default SignIn;
par :
import { connect } from 'react-redux';
{...}
export default connect()(SignIn);
⇒ SignIn est maintenant connecté à Redux.
La fonction connect passe la fonction dispatch en tant que props du composant. dispatch (spatch !) permet de transmettre un JSON action aux reducers. action doit au moins intégrer une clé type qui représente le nom de l'action à considérer.
Dans le composant SignIn, l'appel à dispatch ressemblera à :
this.props.dispatch(
{
type : "CREATE_SESSION",
user: res.user,
token : res.token,
message : res.message
}
)
La fonction doit être appelée à la reception du token. Je te laisse le mettre au bon endroit :).
Pas de reducer pas de Redux
Ton code n'est pas encore exploitable.
On envoie aux reducers une action de type "CREATE_SESSION". Ok mais as-tu créé au moins un reducer ? Donc tu envoies une action à personne !
Tu vas créer ton premier reducer. Pour cela dans le dossier /reducers tu vas créer 2 fichiers :
/reducers
index.js
authReducer.js
Le fichier index.js est le point d'entré des reducers. Il ressemble à :
import {combineReducers} from 'redux'
import authReducer from './authReducer'
const allReducers = combineReducers({
auth: authReducer,
});
export default allReducers;
On référence le reducer authReducer et on le stock dans l'état auth!
Les états de ton application son dans allReducer et l'un des états est auth.
Le fichier authReducer.js est le reducer responsable de l'état auth. Il ressemble ici dans ton cas :
export default function (state = {}, action) {
switch (action.type) {
case "CREATE_SESSION":
return { ...state, token: action.token}
default:
return state;
}
}
Comme tu peux le voir un reducer prend le state précédent (qu'on peut initialiser comme on le souhaite state = {}) et l'action du dispatcher.
De manière assez transparente, suivant le typede l'action, on produit un nouvel état ⇒ ici en définissant le token : { ...state, token: action.token}. Une fois le return exécuté, l'état auth sera mis à jour.
Attention de toujours mettre la condition default dans le switch afin de garantir que le reducer renvoie toujours un état !
Définition du store
Afin de proposer Redux à l'ensemble de l'application, il est nécessaire de configurer un provider dans l'index.js de React. Il faut réaliser les modifications comme suit :
import { createStore } from 'redux';
import { Provider } from 'react-redux';
{...}
const store = createStore(allReducers)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Tu dois encapsuler ton App par un Provider auquel on doit spécifier un store.
Le store est créé à partir des reducers récupérés de /reducers/index.js et grâce à la fonction createStore.
Voilà tu as ton store...
qui est mis à jour par les reducers qui sont mis à jour par les dispatch qui sont passer dans les props du composant à l'aide de
connectqui transforme ainsi le composant en container
... qui est accessible au travers de ton application.
Mapper du state c'est du props
Le store est définit et lorsque tu réussis une authentification, ton état global stocké ressemble à :
{
auth: {
token: "le token"
}
}
Pour notifier que l'utilisateur a bien été authentifié, tu vas devoir le lui notifier si le token devient non vide.
Il faut pour cela récupérer l'état global dans ton composant SignIn. Redux propose, à l'aide de Connect de passer l'état du store en props de ton composant. On passe généralement par une fonction mapStateToProps :
export default connect(mapStateToProps)(SignIn);
Ici connect passe le store (state) Redux à mapStateToProps. On peut donc implémenter la fonction comme ceci :
function mapStateToProps(state) {
return {
flash: state.auth.token,
}
};
Tu peux ainsi passer le token stocké dans l'état auth du store Redux et le passer dans la props flash ⇒ this.props.flash.
Je te laisse afficher le token dans la snackbar :).
You shall not pass !
Tu arrives à d'identifier ? top !
Mais ce qui pourrait être encore pluuuus mieux serait :
-
d'être redirigé vers
/signinsi on n'est pas encore authentifié -
d'être redirigé vers
/profilesi on a un token
Pour cela tu vas devoir implémenter des HOC - Higher Order Component. Un HOC est une fonction qui prend un composant en argument et le retourne (passe plat) si les conditions sont remplies sinon elle redirige l'utilisateur vers une autre route.
Attention Ne jamais oublier que le front n'est pas sécurisable ! L'utilisateur ayant potentiellement accès au code source, il peut hacker l'application et accéder à l'ensemble des pages. Seul le back est sécurisé. Par exemple, un hacker pourra accéder à
/profilemais ne pourra pas récupérer les informations provenant du backend... la page restera donc vierge. Donc le blocage des routes n'a qu'une valeur d'ergonomie. Par exemple, cela ne sert à rien de donner libre accès à/profilesi l'utilisateur n'est pas authentifier car il aura une page vierge.
Tout d'abord tu vas donc bloquer l'accès à /profile au personne non authentifiée. On encapsule le composant dans la Route avec une fonction comme ceci :
<Route exact path="/profile" component={requireAuth(Profile)} />
Tu vas implémenter un hoc au nom de requireAuth. Tu vas créer un dossier hoc/ et y mettre un fichier requireAuth.js.
Ce fichier exporte une fonction avec un composant en argument. Cette fonction renvoie un composant qui sera dans un premier temps celui en argument. Cela donne :
export default function (ComposedComponent) {
return ComposedComponent;
}
Mais ce que l'on souhaite c'est que l'on retourne le composant ou bien que l'on soit redirigé vers /signin. Pour cela le principe est de créer un composant parent qui va gérer tout ça.
import React, { Component } from 'react';
import { browserHistory } from 'react-router';
export default function (ComposedComponent) {
class Authentication extends Component {
componentWillMount() {
if (!this.props.authenticated)
this.props.history.push('/signin');
}
componentWillUpdate() {
if (!this.props.authenticated)
this.props.history.push('/signin');
}
render() {
return <ComposedComponent {...this.props} />
}
}
return Authentication;
}
Ici on crée un composant Authentification qui va rendre ComposedComponent et ses props.
Cependant, avec la mise à jour du DOM on va vérifier que la personne est authentifiée. On regarde la nouvelle props authenticated, si elle est fausse alors on redirige via l'historique de notre routeur.
Mais d'où provient ce nextProps.authenticated ? De Redux bien sûr !
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { browserHistory } from 'react-router';
export default function (ComposedComponent) {
class Authentication extends Component {
componentWillMount() {
if (!this.props.authenticated)
this.props.history.push('/signin');
}
componentWillUpdate() {
if (!this.props.authenticated)
this.props.history.push('/signin');
}
render() {
return <ComposedComponent {...this.props} />
}
}
function mapStateToProps(state) {
return { authenticated: state.auth.token?true:false };
}
return connect(mapStateToProps)(Authentication);
}
Si tout se passe bien, maintenant sans token on est redirigé vers /signin.
Don't authenticate identified people dude !
Dans le même principe que pour l'hoc requireAuth, tu vas créer un hoc requireNotAuth qui va rediriger vers /profile un utilisateur authentifié qui souhaite accéder à /signin.
Je ne peux pas t'aider j'ai piscine. Bon courage
Un redirect du gauche
Je te propose de mettre au bon endroit, dans SignIn la ligne de redirection suivante :
this.props.history.replace("/");
Et de corriger le Switch dans App :
<Switch>
<Redirect exact from='/' to='/profile' />
<Route exact path="/profile" component={requireAuth(Profile)} />
<Route exact path="/signin" component={requireNotAuth(SignIn)} />
<Route exact path="/signup" component={requireNotAuth(SignUp)} />
</Switch>
La requête ça mâche !
Mais ton formulaire doit être vide, ou du moins avec les mauvaises données. Effectivement ta requête récupérant tes données ne peut pas aboutir car :
-
tu n'as toujours pas dit qui tu étais au serveur
-
le serveur attend une personne authentifiée
Il faut donc récupérer le token provenant du store ! Il y a du container là dessous... Je te laisse faire !
Il faut ensuite modifier la requête fetch afin d'y ajouter le token dans le hearder :
fetch('/profile', {
headers: {
'Authorization': 'Bearer ' + this.props.token,
}
})
.then(res => {
if (res.ok)
return res.json()
else
throw new Error(res.statusText)
})
.then(res => {this.setState({ profile: res })})
Je te laisse mettre à jour ta classe Profile.
Si tout se passe bien tu vas observer les données de l'utilisateur connecté dans la page.
Challenge
Chat lounge...
- Réaliser cette quête avec du Red Hot dans les oreilles !
Critères de validation
- Le token doit être stocké dans Redux
- Une fois authentifié on doit être redirigé sur son profil
- Le profil doit afficher les données de l'utilisateur authentifié