Comment faire une authentification JWT avec un SPA angulaire 6

Publié: 2022-03-11

Aujourd'hui, nous verrons à quel point il est facile d'intégrer l'authentification par jeton Web JSON (JWT) dans votre application monopage (SPA) Angular 6 (ou ultérieure). Commençons par un peu de contexte.

Que sont les jetons Web JSON et pourquoi les utiliser ?

La réponse la plus simple et la plus concise ici est qu'ils sont pratiques, compacts et sécurisés. Examinons ces affirmations en détail :

  1. Pratique : L'utilisation d'un JWT pour l'authentification au back-end une fois connecté nécessite de définir un en-tête HTTP, une tâche qui peut être facilement automatisée via une fonction ou une sous-classe, comme nous le verrons plus tard.
  2. Compact : un jeton est simplement une chaîne encodée en base64, contenant quelques champs d'en-tête et une charge utile si nécessaire. Le JWT total est généralement inférieur à 200 octets, même s'il est signé.
  3. Sécurisé : Bien que cela ne soit pas obligatoire, une excellente fonctionnalité de sécurité de JWT est que les jetons peuvent être signés à l'aide du cryptage de paire de clés publique/privée RSA ou du cryptage HMAC à l'aide d'un secret partagé. Cela garantit l'origine et la validité d'un jeton.

Tout cela se résume à un moyen sûr et efficace d'authentifier les utilisateurs, puis de vérifier les appels vers vos points de terminaison d'API sans avoir à analyser de structures de données ni à implémenter votre propre chiffrement.

Théorie des applications

Flux de données typique pour l'authentification et l'utilisation JWT entre les systèmes frontaux et principaux

Donc, avec un peu de contexte, nous pouvons maintenant nous plonger dans la façon dont cela fonctionnerait dans une application réelle. Pour cet exemple, je vais supposer que nous avons un serveur Node.js hébergeant notre API et que nous développons une liste de tâches SPA à l'aide d'Angular 6. Travaillons également avec cette structure d'API :

  • /authPOST (poster le nom d'utilisateur et le mot de passe pour s'authentifier et recevoir en retour un JWT)
  • /todosGET (renvoie une liste d'éléments de la liste de tâches pour l'utilisateur)
  • /todos/{id}GET (renvoie un élément spécifique de la liste de tâches)
  • /usersGET (renvoie une liste d'utilisateurs)

Nous passerons en revue la création de cette application simple sous peu, mais pour l'instant, concentrons-nous sur l'interaction en théorie. Nous avons une page de connexion simple, où l'utilisateur peut entrer son nom d'utilisateur et son mot de passe. Lorsque le formulaire est soumis, il envoie ces informations au point de terminaison /auth . Le serveur Node peut ensuite authentifier l'utilisateur de la manière appropriée (recherche dans la base de données, interrogation d'un autre service Web, etc.), mais en fin de compte, le point de terminaison doit renvoyer un JWT.

Le JWT de cet exemple contiendra quelques revendications réservées et quelques revendications privées . Les revendications réservées sont simplement des paires clé-valeur recommandées par JWT couramment utilisées pour l'authentification, tandis que les revendications privées sont des paires clé-valeur applicables uniquement à notre application :

Réclamations réservées

  • iss : Émetteur de ce jeton. Généralement le nom de domaine complet du serveur, mais peut être n'importe quoi tant que l'application client sait s'y attendre.
  • exp : date et heure d'expiration de ce jeton. Ceci est en secondes depuis le 1er janvier 1970 à minuit GMT (heure Unix).
  • nbf : non valide avant l'horodatage. Pas souvent utilisé, mais donne une limite inférieure pour la fenêtre de validité. Même format que exp .

Revendications privées

  • uid : ID utilisateur de l'utilisateur connecté.
  • role : Rôle attribué à l'utilisateur connecté.

Nos informations seront encodées en base64 et signées en utilisant HMAC avec la clé partagée todo-app-super-shared-secret . Vous trouverez ci-dessous un exemple de ce à quoi ressemble le JWT :

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b2RvYXBpIiwibmJmIjoxNDk4MTE3NjQyLCJleHAiOjE0OTgxMjEyNDIsInVpZCI6MSwicm9sZSI6ImFkbWluIn0.ZDz_1vcIlnZz64nSM28yA1s-4c_iw3Z2ZtP-SgcYRPQ

Cette chaîne est tout ce dont nous avons besoin pour nous assurer que nous avons une connexion valide, pour savoir quel utilisateur est connecté et même pour savoir quel(s) rôle(s) l'utilisateur a.

La plupart des bibliothèques et des applications procèdent au stockage de ce JWT dans localStorage ou sessionStorage pour une récupération facile, mais ce n'est qu'une pratique courante. Ce que vous faites avec le jeton dépend de vous, tant que vous pouvez le fournir pour les futurs appels d'API.

Désormais, chaque fois que le SPA souhaite effectuer un appel vers l'un des points de terminaison d'API protégés, il doit simplement envoyer le jeton dans l'en-tête HTTP d' Authorization .

 Authorization: Bearer {JWT Token}

Remarque : Encore une fois, il s'agit simplement d'une pratique courante. JWT ne prescrit aucune méthode particulière pour s'envoyer au serveur. Vous pouvez également l'ajouter à l'URL ou l'envoyer dans un cookie.

Une fois que le serveur reçoit le JWT, il peut le décoder, assurer la cohérence à l'aide du secret partagé HMAC et vérifier l'expiration à l'aide des champs exp et nbf . Il peut également utiliser le champ iss pour s'assurer qu'il s'agit de la partie émettrice d'origine de ce JWT.

Une fois que le serveur est satisfait de la validité du jeton, les informations stockées dans le JWT peuvent être utilisées. Par exemple, l' uid que nous avons inclus nous donne l'ID de l'utilisateur qui fait la demande. Pour cet exemple particulier, nous avons également inclus le champ de role , qui nous permet de décider si l'utilisateur doit pouvoir accéder à un point de terminaison particulier ou non. (Que vous fassiez confiance à ces informations ou que vous souhaitiez plutôt effectuer une recherche dans la base de données dépend du niveau de sécurité requis.)

 function getTodos(jwtString) { var token = JWTDecode(jwtstring); if( Date.now() < token.nbf*1000) { throw new Error('Token not yet valid'); } if( Date.now() > token.exp*1000) { throw new Error('Token has expired'); } if( token.iss != 'todoapi') { throw new Error('Token not issued here'); } var userID = token.uid; var todos = loadUserTodosFromDB(userID); return JSON.stringify(todos); }

Construisons une application Todo simple

Pour suivre, vous devrez disposer d'une version récente de Node.js (6.x ou version ultérieure), npm (3.x ou version ultérieure) et angular-cli installé. Si vous devez installer Node.js, qui inclut npm, veuillez suivre les instructions ici. Ensuite angular-cli peut être installé en utilisant npm (ou yarn , si vous l'avez installé):

 # installation using npm npm install -g @angular/cli # installation using yarn yarn global add @angular/cli

Je n'entrerai pas dans les détails du passe-partout Angular 6 que nous utiliserons ici, mais pour l'étape suivante, j'ai créé un référentiel Github pour contenir une petite application todo afin d'illustrer la simplicité d'ajout de l'authentification JWT à votre application. Clonez-le simplement en utilisant ce qui suit :

 git clone https://github.com/sschocke/angular-jwt-todo.git cd angular-jwt-todo git checkout pre-jwt

La git checkout pre-jwt passe à une version nommée où JWT n'a pas été implémenté.

Il devrait y avoir deux dossiers à l'intérieur appelés server et client . Le serveur est un serveur Node API qui hébergera notre API de base. Le client est notre application Angular 6.

Le serveur d'API de nœud

Pour commencer, installez les dépendances et démarrez le serveur d'API.

 cd server # installation using npm npm install # or installation using yarn yarn node app.js

Vous devriez pouvoir suivre ces liens et obtenir une représentation JSON des données. Pour l'instant, jusqu'à ce que nous ayons l'authentification, nous avons codé en dur le point de terminaison /todos pour renvoyer les tâches pour userID=1 :

  • http://localhost:4000 : page de test pour voir si le serveur Node est en cours d'exécution
  • http://localhost:4000/api/users : renvoie la liste des utilisateurs du système
  • http://localhost:4000/api/todos : renvoie la liste des tâches pour userID=1

L'application angulaire

Pour démarrer avec l'application cliente, nous devons également installer les dépendances et démarrer le serveur de développement.

 cd client # using npm npm install npm start # using yarn yarn yarn start

Remarque : Selon la vitesse de votre ligne, le téléchargement de toutes les dépendances peut prendre un certain temps.

Si tout se passe bien, vous devriez maintenant voir quelque chose comme ceci lorsque vous naviguez vers http://localhost:4200 :

La version non compatible JWT de notre application Angular Todo List.

Ajout d'une authentification via JWT

Pour ajouter la prise en charge de l'authentification JWT, nous utiliserons certaines bibliothèques standard disponibles qui le rendent plus simple. Vous pouvez, bien sûr, renoncer à ces commodités et tout mettre en œuvre vous-même, mais cela dépasse notre cadre ici.

Tout d'abord, installons une bibliothèque côté client. Il est développé et maintenu par Auth0, qui est une bibliothèque vous permettant d'ajouter une authentification basée sur le cloud à un site Web. L'utilisation de la bibliothèque elle-même ne nécessite pas que vous utilisiez leurs services.

 cd client # installation using npm npm install @auth0/angular-jwt # installation using yarn yarn add @auth0/angular-jwt

Nous verrons le code dans une seconde, mais pendant que nous y sommes, configurons également le côté serveur. Nous utiliserons les bibliothèques body-parser , jsonwebtoken et express-jwt pour que Node comprenne les corps JSON POST et les JWT.

 cd server # installation using npm npm install body-parser jsonwebtoken express-jwt # installation using yarn yarn add body-parser jsonwebtoken express-jwt

Point de terminaison API pour l'authentification

Tout d'abord, nous avons besoin d'un moyen d'authentifier les utilisateurs avant de leur donner un jeton. Pour notre démonstration simple, nous allons simplement configurer un point de terminaison d'authentification fixe avec un nom d'utilisateur et un mot de passe codés en dur. Cela peut être aussi simple ou aussi complexe que votre application l'exige. L'important est de renvoyer un JWT.

Dans server/app.js ajoutez une entrée sous les autres lignes require comme suit :

 const bodyParser = require('body-parser'); const jwt = require('jsonwebtoken'); const expressJwt = require('express-jwt');

Ainsi que les suivants :

 app.use(bodyParser.json()); app.post('/api/auth', function(req, res) { const body = req.body; const user = USERS.find(user => user.username == body.username); if(!user || body.password != 'todo') return res.sendStatus(401); var token = jwt.sign({userID: user.id}, 'todo-app-super-shared-secret', {expiresIn: '2h'}); res.send({token}); });

Il s'agit principalement de code JavaScript de base. Nous obtenons le corps JSON qui a été transmis au point de terminaison /auth , trouvons un utilisateur correspondant à ce nom d'utilisateur, vérifions que nous avons un utilisateur et que le mot de passe correspondent, et renvoyons une erreur HTTP 401 Unauthorized si ce n'est pas le cas.

La partie importante est la génération de jetons, et nous allons la décomposer en ses trois paramètres. La syntaxe de sign est la suivante : jwt.sign(payload, secretOrPrivateKey, [options, callback]) , où :

  • payload est un objet littéral de paires clé-valeur que vous souhaitez encoder dans votre jeton. Ces informations peuvent ensuite être décodées à partir du jeton par toute personne disposant de la clé de déchiffrement. Dans notre exemple, nous encodons le user.id afin que lorsque nous recevons à nouveau le jeton sur le back-end pour l'authentification, nous sachions à quel utilisateur nous avons affaire.
  • secretOrPrivateKey est soit une clé secrète partagée de chiffrement HMAC (c'est ce que nous avons utilisé dans notre application, pour plus de simplicité), soit une clé privée de chiffrement RSA/ECDSA.
  • options représente une variété d'options qui peuvent être transmises à l'encodeur sous la forme de paires clé-valeur. En règle générale, nous spécifions au moins expiresIn (devient la revendication réservée exp ) et l' issuer (demande réservée iss ) afin qu'un jeton ne soit pas valide pour toujours, et le serveur peut vérifier qu'il a bien émis le jeton à l'origine.
  • callback est une fonction à appeler une fois l'encodage terminé, si l'on souhaite gérer l'encodage du jeton de manière asynchrone.

(Vous pouvez également lire plus de détails sur les options et comment utiliser la cryptographie à clé publique au lieu d'une clé secrète partagée.)

Intégration angulaire 6 JWT

Faire fonctionner Angular 6 avec notre JWT est assez simple en utilisant angular-jwt . Ajoutez simplement ce qui suit à client/src/app/app.modules.ts :

 import { JwtModule } from '@auth0/angular-jwt'; // ... export function tokenGetter() { return localStorage.getItem('access_token'); } @NgModule({ // ... imports: [ BrowserModule, AppRoutingModule, HttpClientModule, FormsModule, // Add this import here JwtModule.forRoot({ config: { tokenGetter: tokenGetter, whitelistedDomains: ['localhost:4000'], blacklistedRoutes: ['localhost:4000/api/auth'] } }) ], // ... }

C'est essentiellement tout ce qui est nécessaire. Bien sûr, nous avons encore du code à ajouter pour effectuer l'authentification initiale, mais la bibliothèque angular-jwt se charge d'envoyer le jeton avec chaque requête HTTP.

  • La fonction tokenGetter() fait exactement ce qu'elle dit, mais la façon dont elle est implémentée dépend entièrement de vous. Nous avons choisi de retourner le jeton que nous sauvegardons dans localStorage . Vous êtes bien sûr libre de fournir toute autre méthode que vous désirez, tant qu'elle renvoie la chaîne encodée du jeton Web JSON .
  • L'option whiteListedDomains existe afin que vous puissiez restreindre les domaines auxquels le JWT est envoyé, afin que les API publiques ne reçoivent pas également votre JWT.
  • L'option blackListedRoutes vous permet de spécifier des itinéraires spécifiques qui ne doivent pas recevoir le JWT même s'ils se trouvent sur un domaine en liste blanche. Par exemple, le point de terminaison d'authentification n'a pas besoin de le recevoir car cela ne sert à rien : le jeton est généralement nul lorsqu'il est appelé de toute façon.

Faire en sorte que tout fonctionne ensemble

À ce stade, nous avons un moyen de générer un JWT pour un utilisateur donné en utilisant le point de terminaison /auth sur notre API, et nous avons la plomberie effectuée sur Angular pour envoyer un JWT avec chaque requête HTTP. Très bien, mais vous pourriez souligner qu'absolument rien n'a changé pour l'utilisateur. Et vous auriez raison. Nous pouvons toujours accéder à chaque page de notre application et nous pouvons appeler n'importe quel point de terminaison d'API sans même envoyer de JWT. Pas bon!

Nous devons mettre à jour notre application client pour nous préoccuper de savoir qui est connecté, et également mettre à jour notre API pour exiger un JWT. Commençons.

Nous aurons besoin d'un nouveau composant Angular pour nous connecter. Par souci de brièveté, je vais garder cela aussi simple que possible. Nous aurons également besoin d'un service qui gérera toutes nos exigences d'authentification et d'un garde angulaire pour protéger les routes qui ne devraient pas être accessibles avant la connexion. Nous ferons ce qui suit dans le contexte de l'application cliente.

 cd client ng g component login --spec=false --inline-style ng g service auth --flat --spec=false ng g guard auth --flat --spec=false

Cela aurait dû générer quatre nouveaux fichiers dans le dossier client :

 src/app/login/login.component.html src/app/login/login.component.ts src/app/auth.service.ts src/app/auth.guard.ts

Ensuite, nous devons fournir le service d'authentification et protéger notre application. Mettre à jour client/src/app/app.modules.ts :

 import { AuthService } from './auth.service'; import { AuthGuard } from './auth.guard'; // ... providers: [ TodoService, UserService, AuthService, AuthGuard ],

Et puis mettez à jour le routage dans client/src/app/app-routing.modules.ts pour utiliser la protection d'authentification et fournir une route pour le composant de connexion.

 // ... import { LoginComponent } from './login/login.component'; import { AuthGuard } from './auth.guard'; const routes: Routes = [ { path: 'todos', component: TodoListComponent, canActivate: [AuthGuard] }, { path: 'users', component: UserListComponent, canActivate: [AuthGuard] }, { path: 'login', component: LoginComponent}, // ...

Enfin, mettez à jour client/src/app/auth.guard.ts avec le contenu suivant :

 import { Injectable } from '@angular/core'; import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { constructor(private router: Router) { } canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) { if (localStorage.getItem('access_token')) { return true; } this.router.navigate(['login']); return false; } }

Pour notre application de démonstration, nous vérifions simplement l'existence d'un JWT dans le stockage local. Dans les applications du monde réel, vous décodez le jeton et vérifiez sa validité, son expiration, etc. Par exemple, vous pouvez utiliser JwtHelperService pour cela.

À ce stade, notre application Angular vous redirigera désormais toujours vers la page de connexion puisque nous n'avons aucun moyen de nous connecter. Rectifions cela, en commençant par le service d'authentification dans client/src/app/auth.service.ts :

 import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable() export class AuthService { constructor(private http: HttpClient) { } login(username: string, password: string): Observable<boolean> { return this.http.post<{token: string}>('/api/auth', {username: username, password: password}) .pipe( map(result => { localStorage.setItem('access_token', result.token); return true; }) ); } logout() { localStorage.removeItem('access_token'); } public get loggedIn(): boolean { return (localStorage.getItem('access_token') !== null); } }

Notre service d'authentification n'a que deux fonctions, login et logout :

  • login POST envoie le nom d' username et le mot de password fournis à notre serveur principal et définit le access_token dans localStorage s'il en reçoit un en retour. Par souci de simplicité, il n'y a pas de gestion des erreurs ici.
  • logout efface simplement access_token de localStorage , ce qui nécessite l'acquisition d'un nouveau jeton avant de pouvoir accéder à nouveau à quoi que ce soit d'autre.
  • loggedIn est une propriété booléenne que nous pouvons rapidement utiliser pour déterminer si l'utilisateur est connecté ou non.

Et enfin, le composant de connexion. Ceux-ci n'ont aucun rapport avec le travail réel avec JWT, alors n'hésitez pas à copier et coller dans client/src/app/login/login.components.html :

 <h4 *ngIf="error">{{error}}</h4> <form (ngSubmit)="submit()"> <div class="form-group col-3"> <label for="username">Username</label> <input type="text" name="username" class="form-control" [(ngModel)]="username" /> </div> <div class="form-group col-3"> <label for="password">Password</label> <input type="password" name="password" class="form-control" [(ngModel)]="password" /> </div> <div class="form-group col-3"> <button class="btn btn-primary" type="submit">Login</button> </div> </form>

Et client/src/app/login/login.components.ts aura besoin de :

 import { Component, OnInit } from '@angular/core'; import { AuthService } from '../auth.service'; import { Router } from '@angular/router'; import { first } from 'rxjs/operators'; @Component({ selector: 'app-login', templateUrl: './login.component.html' }) export class LoginComponent { public username: string; public password: string; public error: string; constructor(private auth: AuthService, private router: Router) { } public submit() { this.auth.login(this.username, this.password) .pipe(first()) .subscribe( result => this.router.navigate(['todos']), err => this.error = 'Could not authenticate' ); } }

Voila, notre exemple de connexion Angular 6 :

L'écran de connexion de notre exemple d'application Angular Todo List.

À ce stade, nous devrions pouvoir nous connecter (en utilisant jemma , paul ou sebastian avec le mot de passe todo ) et revoir tous les écrans. Mais notre application affiche les mêmes en-têtes de navigation et aucun moyen de se déconnecter quel que soit l'état actuel. Réglons cela avant de passer à la correction de notre API.

Dans client/src/app/app.component.ts , remplacez tout le fichier par ce qui suit :

 import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from './auth.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { constructor(private auth: AuthService, private router: Router) { } logout() { this.auth.logout(); this.router.navigate(['login']); } }

Et pour client/src/app/app.component.html remplacez la section <nav> par ce qui suit :

 <nav class="nav nav-pills"> <a class="nav-link" routerLink="todos" routerLinkActive="active" *ngIf="auth.loggedIn">Todo List</a> <a class="nav-link" routerLink="users" routerLinkActive="active" *ngIf="auth.loggedIn">Users</a> <a class="nav-link" routerLink="login" routerLinkActive="active" *ngIf="!auth.loggedIn">Login</a> <a class="nav-link" (click)="logout()" href="#" *ngIf="auth.loggedIn">Logout</a> </nav>

Nous avons rendu notre navigation sensible au contexte pour qu'elle n'affiche que certains éléments selon que l'utilisateur est connecté ou non. auth.loggedIn peut, bien sûr, être utilisé partout où vous pouvez importer le service d'authentification.

Sécuriser l'API

Vous pensez peut-être que c'est génial… tout semble fonctionner à merveille . Mais essayez de vous connecter avec les trois noms d'utilisateur différents et vous remarquerez quelque chose : ils renvoient tous la même liste de tâches. Si nous jetons un coup d'œil à notre serveur API, nous pouvons voir que chaque utilisateur a en fait sa propre liste d'éléments, alors quoi de neuf ?

Eh bien, rappelez-vous que lorsque nous avons commencé, nous avons codé notre point de terminaison API /todos pour toujours renvoyer la liste de tâches pour userID=1 . En effet, nous n'avions aucun moyen de savoir qui était l'utilisateur actuellement connecté.

Maintenant que nous le faisons, voyons à quel point il est facile de sécuriser nos points de terminaison et d'utiliser les informations encodées dans le JWT pour fournir l'identité d'utilisateur requise. Au départ, ajoutez cette ligne à votre fichier server/app.js juste en dessous du dernier appel app.use() :

 app.use(expressJwt({secret: 'todo-app-super-shared-secret'}).unless({path: ['/api/auth']}));

Nous utilisons le middleware express-jwt , lui disons quel est le secret partagé et spécifions un tableau de chemins pour lesquels il ne devrait pas nécessiter de JWT. Et c'est tout. Pas besoin de toucher à chaque point de terminaison, de créer des déclarations if partout, ou quoi que ce soit.

En interne, le middleware fait quelques hypothèses. Par exemple, il suppose que l'en-tête HTTP d' Authorization suit le modèle JWT commun de Bearer {token} . (La bibliothèque a cependant de nombreuses options pour personnaliser son fonctionnement si ce n'est pas le cas. Voir Utilisation express-jwt pour plus de détails.)

Notre deuxième objectif est d'utiliser les informations codées JWT pour savoir qui passe l'appel. Une fois de plus, express-jwt vient à la rescousse. Dans le cadre de la lecture et de la vérification du jeton, il définit la charge utile codée que nous avons envoyée dans le processus de signature à la variable req.user dans Express. Nous pouvons ensuite l'utiliser pour accéder immédiatement à l'une des variables que nous avons stockées. Dans notre cas, nous définissons userID égal à l'ID de l'utilisateur authentifié, et en tant que tel, nous pouvons l'utiliser directement comme req.user.userID .

Mettez à nouveau à jour server/app.js et modifiez le point de terminaison /todos pour qu'il se lise comme suit :

 res.send(getTodos(req.user.userID)); 

Notre application Angular Todo List utilise le JWT pour afficher la liste de tâches de l'utilisateur connecté, plutôt que celle que nous avions codée en dur plus tôt.

Et c'est tout. Notre API est désormais sécurisée contre tout accès non autorisé et nous pouvons déterminer en toute sécurité qui est notre utilisateur authentifié sur n'importe quel point de terminaison. Notre application cliente a également un processus d'authentification simple, et tous les services HTTP que nous écrivons et qui appellent notre point de terminaison API auront automatiquement un jeton d'authentification attaché.

Si vous avez cloné le référentiel Github et que vous souhaitez simplement voir le résultat final en action, vous pouvez consulter le code dans sa forme finale en utilisant :

 git checkout with-jwt

J'espère que vous avez trouvé cette procédure pas à pas utile pour ajouter l'authentification JWT à vos propres applications Angular. Merci d'avoir lu!

Connexes : Tutoriel sur les jetons Web JSON : un exemple dans Laravel et AngularJS