Vous êtes sur la page 1sur 35

Angular : JWT et HttpInterceptor

Achref El Mouelhi

Docteur de l’université d’Aix-Marseille


Chercheur en programmation par contrainte (IA)
Ingénieur en génie logiciel

elmouelhi.achref@gmail.com

H & H: Research and Training 1 / 30


Plan

1 Introduction

2 JWT

3 HttpInterceptor

4 Guard CanActivate

5 Déconnexion

H & H: Research and Training 2 / 30


Introduction

Angular
Objectif de ce chapitre

Considérons les fichiers suivant qui permettent de communiquer avec une ressource
REST (PHP, Java, C#...) en lecture et écriture (le code est donné dans les slides
suivantes)

H I ©
EL
app.module.ts
U
MO
personne.ts

E L
personne.component.ts
f
ch r e
personne.component.html

© A
personne.component.css
personne.service.ts
Les ressources REST nécessite une authentification (avec JWT) et une autorisation

H & H: Research and Training 3 / 30


Introduction

Angular
Objectif de ce chapitre

Considérons les fichiers suivant qui permettent de communiquer avec une ressource
REST (PHP, Java, C#...) en lecture et écriture (le code est donné dans les slides
suivantes)

H I ©
EL
app.module.ts
U
MO
personne.ts

E L
personne.component.ts
f
ch r e
personne.component.html

© A
personne.component.css
personne.service.ts
Les ressources REST nécessite une authentification (avec JWT) et une autorisation

L’URL dans le service est à modifier.

H & H: Research and Training 3 / 30


Introduction

Contenu de app.module.ts

import { BrowserModule } from ’@angular/platform-browser’;


import { NgModule } from ’@angular/core’;
import { FormsModule } from ’@angular/forms’;
import { HttpClientModule } from ’@angular/common/http’;

//+ les autres imports

@NgModule({
H I ©
EL
declarations: [
AppComponent,
O U
LM
AdresseComponent,
PersonneComponent,
FormulaireComponent
r e f E
ch
],

©A
imports: [
BrowserModule,
FormsModule,
HttpClientModule
],
providers: [PersonneService],
bootstrap: [AppComponent]
})
export class AppModule { }

H & H: Research and Training 4 / 30


Introduction

Angular

Contenu de personne.ts
H I ©
export interface Personne {
UEL
id?: number;
O
nom?: string;
f E LM
prenom?: string;
ch r e
©A
}

H & H: Research and Training 5 / 30


Introduction

Angular
Contenu de personne.service.ts

import { Injectable } from ’@angular/core’;


import { HttpClient } from ’@angular/common/http’;
import { Personne } from ’../interfaces/personne’;

@Injectable({
H I ©
EL
providedIn: ’root’
})
export class PersonneService {
O U
f E LM
url: string = "http://localhost:8000/ws/personnes";

ch r e
©A
constructor(private http: HttpClient) { }

getAll() {
return this.http.get<Array<Personne>>(this.url);
}
addPerson(p: Personne) {
return this.http.post(this.url, p);
}
}

H & H: Research and Training 6 / 30


Introduction

Angular
Contenu de personne.component.html

<h3> Pour ajouter une personne</h3>


<form #monForm=ngForm (ngSubmit)=ajouterPersonne()>
<div>
Nom : <input type=text name=nom [(ngModel)]=personne.nom required #nom="ngModel">
</div>
<div [hidden]="nom.valid || nom.pristine">
Le nom est obligatoire
</div>
H I ©
EL
<div>

">
O U
Prénom : <input type=text name=prenom [(ngModel)]=personne.prenom required #prenom="ngModel

LM
</div>
<div [hidden]="prenom.valid || prenom.pristine">
Le prénom est obligatoire
</div>
r e f E
<div>
ch
©A
<button [disabled]=!monForm.valid>
ajouter
</button>
</div>
</form>

<h3> Liste de personnes </h3>


<ul>
<li *ngFor="let elt of personnes">
{{ elt.prenom }} {{ elt.nom }}
</li>
</ul>

H & H: Research and Training 7 / 30


Introduction

Angular

Contenu de personne.component.css
.ng-invalid:not(form){ H I ©
border-left: 5px solid red;
UEL
O
}
.ng-valid:not(form){
f E LM
ch
border-left: 5px solid green;r e
}
©A

H & H: Research and Training 8 / 30


Introduction

Contenu de personne.component.ts

// les imports
@Component({
selector: ’app-personne’,
templateUrl: ’./personne.component.html’,
styleUrls: [’./personne.component.css’]
})
export class PersonneComponent implements OnInit {
personne: Personne = {};
H I ©
EL
personnes: Array <Personne> = [];

U
constructor(private personneService: PersonneService) { }
O
LM
ngOnInit() {
this.personneService.getAll().subscribe(res => {
this.personnes = res;
r e f E
ch
});

©A
}
ajouterPersonne() {
this.personneService.addPerson(this.personne).subscribe(res => {
this.personneService.getAll().subscribe(result => {
this.personnes = result;
});
});
}
}

H & H: Research and Training 9 / 30


Introduction

Angular

Exercice
Pour chaque personne, ajoutez
H I ©
U EL la personne
un bouton supprimer qui permet de supprimer
associée de la base de données O
L M
un lien modifier qui
r e E
f d’afficher les données relatives à la
permet
A
personne associc h
ée dans un formulaire du composant
©
edit-personne afin de permettre la modification de ces
données.

H & H: Research and Training 10 / 30


JWT

Angular

Pour commencer
H I ©
EL
Créons une interface user : ng g i interfaces/user
U
Créons un composant authL MgOc composants/auth
: ng
r e f E
A ch
Créons un service auth : ng g s services/auth
©

H & H: Research and Training 11 / 30


JWT

Angular

Contenu de user.ts
H I ©
export interface User {
UEL
id?: number;
O
email?: string;
f ELM
password?: string;
ch r e
©A
}

H & H: Research and Training 12 / 30


JWT

Angular
Commençons par modifier le contenu de auth.service.ts en ajoutons une
méthode login qui utilise la méthode HTTP POST pour interroger le backend
import { Injectable } from ’@angular/core’;
import { HttpClient } from ’@angular/common/http’;
import { User } from ’../interfaces/user’;

H I ©
@Injectable({
providedIn: ’root’
U EL
O
LM
})
export class AuthService {
r e f E
A ch
private url = ’http://localhost:8000/authentication_token’;
©
constructor(private http: HttpClient) { }

login(user: User) {
return this.http.post(this.url, user);
}
}

H & H: Research and Training 13 / 30


JWT

Angular
Préparons le formulaire d’authentification (auth.component.html)
<h3> Authentication</h3>
<form #monForm=ngForm (ngSubmit)=login()>
<div>
Email : <input type=text name=email [(ngModel)]=user.
email required>
H I ©
</div>
U EL
O
<div>

f ELM
Mot de passe : <input type=text name=password [(ngModel

ch r e
)]=user.password required>

©A
</div>

<div>
<button>
connexion
</button>
</div>
</form>

H & H: Research and Training 14 / 30


JWT

Angular
Dans auth.component.ts, on utilise la méthode login de auth.service.ts. si on reçoit un token l’utilisateur existe
dans la base (on ajoute donc le token au localstorage)

import { Component, OnInit } from ’@angular/core’;


import { User } from ’src/app/interfaces/user’;
import { AuthService } from ’src/app/services/auth.service’;
import { Router } from ’@angular/router’;

@Component({
selector: ’app-auth’,

H I ©
EL
templateUrl: ’./auth.component.html’,
styleUrls: [’./auth.component.css’]
})
O U
LM
export class AuthComponent implements OnInit {
user: User = {};

r e f E
constructor(private authService: AuthService, private router: Router) { }

ch
ngOnInit(): void {

©A
}
login() {
this.authService.login(this.user).subscribe(res => {
if (res[’token’]) {
localStorage.setItem(’token’, res[’token’]);
this.router.navigateByUrl(’/personne’);
}
else {
this.router.navigateByUrl(’/auth’);
}
});
}
}

H & H: Research and Training 15 / 30


JWT

Angular
Modifions maintenant le contenu de personne.service.ts pour utiliser le token

import { Injectable } from ’@angular/core’;


import { HttpClient, HttpHeaders } from ’@angular/common/http’;
import { Personne } from ’../interfaces/personne’;
import { map } from ’rxjs/operators’;

@Injectable({

})
providedIn: ’root’

H I ©
EL
export class PersonneService {

private url = ’http://localhost:8000/ws/personnes’;


O U
headers: HttpHeaders;

f E LM
r e
constructor(private http: HttpClient) {

ch
const token = localStorage.getItem(’token’);

©A
this.headers = new HttpHeaders().set(’Authorization’, ’Bearer ’ + token);
}

getAll() {
return this.http.get(this.url, { headers: this.headers }).pipe(map(data => data[’hydra:
member’]));
}

addPerson(p: Personne) {
return this.http.post(this.url, p, { headers: this.headers });
}
}

H & H: Research and Training 16 / 30


JWT

Angular

Deux problèmes avec la solution précédente

Code répétitif (le HttpHeaders dans toutes les requêtes HTTP)

H I ©
EL
Sans authentification, on peut accéder au composant personne

O U
f ELM
ch r e
©A

H & H: Research and Training 17 / 30


JWT

Angular

Deux problèmes avec la solution précédente

Code répétitif (le HttpHeaders dans toutes les requêtes HTTP)

H I ©
EL
Sans authentification, on peut accéder au composant personne

O U
f ELM
ch r e
©A
Solutions
pour le premier problème : HttpInterceptor
pour le deuxième : Guard

H & H: Research and Training 17 / 30


HttpInterceptor

Angular

H I ©
U EL
Commençons par créer un intercepteur auth dans services

L
ng g interceptor services/auth MO
r e f E
A ch
©

H & H: Research and Training 18 / 30


HttpInterceptor

Angular

Code généré de auth.interceptor.ts

import { Injectable } from ’@angular/core’;


import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor }
from
import {
’@angular/common/http’;
Observable } from ’rxjs’;
H I ©
UEL
@Injectable()
O
f E LM
export class AuthInterceptor implements HttpInterceptor {

constructor() {}
ch r e
©A
intercept(request: HttpRequest<unknown>, next: HttpHandler):
Observable<HttpEvent<unknown>> {
return next.handle(request);
}
}

H & H: Research and Training 19 / 30


HttpInterceptor

Angular
Déclarons l’intercepteur dans app.module.ts

import { BrowserModule } from ’@angular/platform-browser’;


import { NgModule } from ’@angular/core’;
import { HttpClientModule, HTTP_INTERCEPTORS } from ’@angular/common/http’;
import { FormsModule } from ’@angular/forms’;

©
import { AppRoutingModule } from ’./app-routing.module’;
import { AppComponent } from ’./app.component’;

H I
EL
import { PersonneComponent } from ’./composants/personne/personne.component’;
import { AuthComponent } from ’./composants/auth/auth.component’;
import {
U
AuthInterceptor } from ’./services/auth.interceptor’;

O
LM
@NgModule({
declarations: [
AppComponent,

r e f E
ch
PersonneComponent,
AuthComponent

©A
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule
],
providers: [{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }],
bootstrap: [AppComponent]
})
export class AppModule { }

H & H: Research and Training 20 / 30


HttpInterceptor

Demandons à l’intercepteur de modifier toutes les requêtes en ajoutant le token sauf pour
celle qui demande le jeton (à l’authentification)
import { Injectable } from ’@angular/core’;
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from ’
@angular/common/http’;
import { Observable } from ’rxjs’;

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
H I ©
constructor() { }
U EL
O
f
Observable<HttpEvent<unknown>> {E LM
intercept(request: HttpRequest<unknown>, next: HttpHandler):

ch r e
if (request.url !== ’http://localhost:8000/authentication_token’) {

©A
const token = localStorage.getItem(’token’);
request = request.clone({
setHeaders: {
Authorization: ’Bearer ’ + token
}
});
}
return next.handle(request);
}
}
H & H: Research and Training 21 / 30
HttpInterceptor

Modifions maintenant personne.service.ts pour ne plus ajouter le token

import { Injectable } from ’@angular/core’;


import { HttpClient } from ’@angular/common/http’;
import { Personne } from ’../interfaces/personne’;
import { map } from ’rxjs/operators’;

@Injectable({
providedIn: ’root’
})
H I ©
EL
export class PersonneService {

O U
LM
private url = ’http://localhost:8000/ws/personnes’;

e f E
constructor(private http: HttpClient) { }
r
ch
©A
getAll() {
return this.http.get(this.url).pipe(map(data => data[’hydra:member’
]));
}

addPerson(p: Personne) {
return this.http.post(this.url, p);
}
}

H & H: Research and Training 22 / 30


Guard CanActivate

Angular

Pour notre exemple, on va créer une garde qui vérifie si un


utilisateur est authentifié avant de charger certaines routes
ng g g guards/auth
H I ©
UEL
O
f E LM
ch r e
©A

H & H: Research and Training 23 / 30


Guard CanActivate

Angular

Pour notre exemple, on va créer une garde qui vérifie si un


utilisateur est authentifié avant de charger certaines routes
ng g g guards/auth
H I ©
UEL
O
f E LM
Dans le menu qui s’affiche
ch r e
©A
Pointer sur CanActivate
Puis cliquer une première fois sur espace et une deuxième sur
entrée

H & H: Research and Training 23 / 30


Guard CanActivate

Angular

On peut aussi préciser dans la commande l’interface à


implémenter
H I ©
EL
ng g g guards/auth --implements CanActivate
U
L MO
r e f E
A ch
©

H & H: Research and Training 24 / 30


Guard CanActivate

Angular

On peut aussi préciser dans la commande l’interface à


implémenter
H I ©
EL
ng g g guards/auth --implements CanActivate
U
L MO
r e f E
Le résultat A ch
©
CREATE src/app/guards/auth.guard.spec.ts (346 bytes)
CREATE src/app/guards/auth.guard.ts (456 bytes)

H & H: Research and Training 24 / 30


Guard CanActivate

Contenu de auth.guard.ts
import { Injectable } from ’@angular/core’;
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot,
UrlTree } from ’@angular/router’;
import { Observable } from ’rxjs’;

@Injectable({
providedIn: ’root’
})
H I ©
EL
export class AuthGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
O U
f E LM
state: RouterStateSnapshot): Observable<boolean | UrlTree> |
Promise<boolean | UrlTree> | boolean | UrlTree {
return true;
ch r e
©A
}
}

H & H: Research and Training 25 / 30


Guard CanActivate

Contenu de auth.guard.ts
import { Injectable } from ’@angular/core’;
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot,
UrlTree } from ’@angular/router’;
import { Observable } from ’rxjs’;

@Injectable({
providedIn: ’root’
})
H I ©
EL
export class AuthGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
O U
f E LM
state: RouterStateSnapshot): Observable<boolean | UrlTree> |
Promise<boolean | UrlTree> | boolean | UrlTree {
return true;
ch r e
©A
}
}

ActivatedRouteSnapshot : contient des informations comme les paramètres envoyés


pour la route demandée...

RouterStateSnapshot :contient des informations comme l’URL de la route demandée

La méthode canActivate ne fait aucun contrôle car elle retourne toujours true

H & H: Research and Training 25 / 30


Guard CanActivate

Angular
Modifions le contenu de auth.guard.ts pour rediriger vers la page d’authentification s’il n’y a pas de jeton en session

import { Injectable } from ’@angular/core’;


import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from ’
@angular/router’;
import { Observable } from ’rxjs’;

@Injectable({

})
providedIn: ’root’

H I ©
EL
export class AuthGuard implements CanActivate {

constructor(private router: Router) { }


O U
canActivate(
next: ActivatedRouteSnapshot,
f E LM
r e
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> |
boolean | UrlTree {
ch
if (token) {©A
const token = localStorage.getItem(’token’);

return true;
}
else {
this.router.navigateByUrl(’/auth’);
return false;
}
}

H & H: Research and Training 26 / 30


Guard CanActivate

Associons AuthGuard aux différentes routes dans app-routing.module.ts

import { NgModule } from ’@angular/core’;


import { Routes, RouterModule } from ’@angular/router’;
import { PersonneComponent } from ’./composants/personne/personne.
component’;
import { EditPersonneComponent } from ’./composants/edit-personne/edit-
personne.component’;

import { AuthGuard } from ’./services/auth.guard’;


H I ©
import { AuthComponent } from ’./composants/auth/auth.component’;

UEL
const routes: Routes = [
O
LM
{ path: ’auth’, component: AuthComponent },

canActivate: [AuthGuard] },
r e E
{ path: ’editPersonne/:id’, component: EditPersonneComponent,
f
ch
{ path: ’personne’, component: PersonneComponent, canActivate: [

];
AuthGuard] },
©A
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

H & H: Research and Training 27 / 30


Guard CanActivate

Angular

Pour éviter les répétitions dans app-routing.module.ts

import { NgModule } from ’@angular/core’;


import { Routes, RouterModule } from ’@angular/router’;
import { PersonneComponent } from ’./composants/personne/personne.component’;

©
import { EditPersonneComponent } from ’./composants/edit-personne/edit-personne.component’;
import { AuthComponent } from ’./composants/auth/auth.component’;

H I
EL
import { AuthGuard } from ’./services/auth.guard’;

const routes: Routes = [

O U
LM
{ path: ’auth’, component: AuthComponent },
{

e f E
path: ’’, canActivate: [AuthGuard], children: [

r
ch
{ path: ’editPersonne/:id’, component: EditPersonneComponent },
{ path: ’personne’, component: PersonneComponent }

©A
]}
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

H & H: Research and Training 28 / 30


Déconnexion

Angular

Pour se déconnecter, il suffit de


H I ©
EL
supprimer le token du localStorage
M OU
f
rediriger l’utilisateur vers E
la L d’authentification
page
chr e
© A

H & H: Research and Training 29 / 30


Déconnexion

Angular

Exercice
Dans le composant app-component, on veut afficher H I ©
UEL
O
un bouton déconnexion si un jeton est stocké dans le
WebStorage
f E LM
r e
ch qui redirige vers la page d’authentification
un lien connexion
sinon. © A

H & H: Research and Training 30 / 30