angular : jwt et httpinterceptorintroduction angular objectif de ce chapitre considerons les...
TRANSCRIPT
Angular : JWT et HttpInterceptor
Achref El Mouelhi
Docteur de l’universite d’Aix-MarseilleChercheur en programmation par contrainte (IA)
Ingenieur en genie logiciel
H & H: Research and Training 1 / 30
Plan
1 Introduction
2 JWT
3 HttpInterceptor
4 Guard CanActivate
5 Deconnexion
H & H: Research and Training 2 / 30
© Achref EL MOUELHI ©
Introduction
Angular
Objectif de ce chapitre
Considerons les fichiers suivant qui permettent de communiquer avec une ressourceREST (PHP, Java, C#...) en lecture et ecriture (le code est donne dans les slidessuivantes)
app.module.ts
personne.ts
personne.component.ts
personne.component.html
personne.component.css
personne.service.ts
Les ressources REST necessite une authentification (avec JWT) et une autorisation
L’URL dans le service est a modifier.
H & H: Research and Training 3 / 30
© Achref EL MOUELHI ©
Introduction
Angular
Objectif de ce chapitre
Considerons les fichiers suivant qui permettent de communiquer avec une ressourceREST (PHP, Java, C#...) en lecture et ecriture (le code est donne dans les slidessuivantes)
app.module.ts
personne.ts
personne.component.ts
personne.component.html
personne.component.css
personne.service.ts
Les ressources REST necessite une authentification (avec JWT) et une autorisation
L’URL dans le service est a modifier.
H & H: Research and Training 3 / 30
© Achref EL MOUELHI ©
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({declarations: [
AppComponent,AdresseComponent,PersonneComponent,FormulaireComponent
],imports: [
BrowserModule,FormsModule,HttpClientModule
],providers: [PersonneService],bootstrap: [AppComponent]
})export class AppModule { }
H & H: Research and Training 4 / 30
© Achref EL MOUELHI ©
Introduction
Angular
Contenu de personne.ts
export interface Personne {id?: number;nom?: string;prenom?: string;
}
H & H: Research and Training 5 / 30
© Achref EL MOUELHI ©
Introduction
AngularContenu de personne.service.ts
import { Injectable } from ’@angular/core’;import { HttpClient } from ’@angular/common/http’;import { Personne } from ’../interfaces/personne’;
@Injectable({providedIn: ’root’
})export class PersonneService {
url: string = "http://localhost:8000/ws/personnes";
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
© Achref EL MOUELHI ©
Introduction
AngularContenu 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><div>Prenom : <input type=text name=prenom [(ngModel)]=personne.prenom required #prenom="ngModel
"></div><div [hidden]="prenom.valid || prenom.pristine">
Le prenom est obligatoire</div>
<div><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
© Achref EL MOUELHI ©
Introduction
Angular
Contenu de personne.component.css
.ng-invalid:not(form){border-left: 5px solid red;
}.ng-valid:not(form){
border-left: 5px solid green;}
H & H: Research and Training 8 / 30
© Achref EL MOUELHI ©
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 = {};personnes: Array <Personne> = [];constructor(private personneService: PersonneService) { }ngOnInit() {
this.personneService.getAll().subscribe(res => {this.personnes = res;
});}ajouterPersonne() {
this.personneService.addPerson(this.personne).subscribe(res => {this.personneService.getAll().subscribe(result => {this.personnes = result;
});});
}}
H & H: Research and Training 9 / 30
© Achref EL MOUELHI ©
Introduction
Angular
Exercice
Pour chaque personne, ajoutez
un bouton supprimer qui permet de supprimer la personneassociee de la base de donnees
un lien modifier qui permet d’afficher les donnees relatives a lapersonne associee dans un formulaire du composantedit-personne afin de permettre la modification de cesdonnees.
H & H: Research and Training 10 / 30
© Achref EL MOUELHI ©
JWT
Angular
Pour commencer
Creons une interface user : ng g i interfaces/user
Creons un composant auth : ng g c composants/auth
Creons un service auth : ng g s services/auth
H & H: Research and Training 11 / 30
© Achref EL MOUELHI ©
JWT
Angular
Contenu de user.ts
export interface User {id?: number;email?: string;password?: string;
}
H & H: Research and Training 12 / 30
© Achref EL MOUELHI ©
JWT
AngularCommencons par modifier le contenu de auth.service.ts en ajoutons unemethode login qui utilise la methode HTTP POST pour interroger le backend
import { Injectable } from ’@angular/core’;import { HttpClient } from ’@angular/common/http’;import { User } from ’../interfaces/user’;
@Injectable({providedIn: ’root’
})export class AuthService {
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
© Achref EL MOUELHI ©
JWT
AngularPreparons 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></div>
<div>Mot de passe : <input type=text name=password [(ngModel
)]=user.password required></div>
<div><button>
connexion</button>
</div></form>
H & H: Research and Training 14 / 30
© Achref EL MOUELHI ©
JWT
AngularDans auth.component.ts, on utilise la methode login de auth.service.ts. si on recoit un token l’utilisateur existedans 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’,templateUrl: ’./auth.component.html’,styleUrls: [’./auth.component.css’]
})export class AuthComponent implements OnInit {
user: User = {};constructor(private authService: AuthService, private router: Router) { }
ngOnInit(): void {}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
© Achref EL MOUELHI ©
JWT
AngularModifions 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’
})export class PersonneService {
private url = ’http://localhost:8000/ws/personnes’;
headers: HttpHeaders;
constructor(private http: HttpClient) {const token = localStorage.getItem(’token’);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
© Achref EL MOUELHI ©
JWT
Angular
Deux problemes avec la solution precedente
Code repetitif (le HttpHeaders dans toutes les requetes HTTP)
Sans authentification, on peut acceder au composant personne
Solutions
pour le premier probleme : HttpInterceptor
pour le deuxieme : Guard
H & H: Research and Training 17 / 30
© Achref EL MOUELHI ©
JWT
Angular
Deux problemes avec la solution precedente
Code repetitif (le HttpHeaders dans toutes les requetes HTTP)
Sans authentification, on peut acceder au composant personne
Solutions
pour le premier probleme : HttpInterceptor
pour le deuxieme : Guard
H & H: Research and Training 17 / 30
© Achref EL MOUELHI ©
HttpInterceptor
Angular
Commencons par creer un intercepteur auth dans services
ng g interceptor services/auth
H & H: Research and Training 18 / 30
© Achref EL MOUELHI ©
HttpInterceptor
Angular
Code genere de auth.interceptor.ts
import { Injectable } from ’@angular/core’;import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor }
from ’@angular/common/http’;import { Observable } from ’rxjs’;
@Injectable()export class AuthInterceptor implements HttpInterceptor {
constructor() {}
intercept(request: HttpRequest<unknown>, next: HttpHandler):Observable<HttpEvent<unknown>> {return next.handle(request);
}}
H & H: Research and Training 19 / 30
© Achref EL MOUELHI ©
HttpInterceptor
AngularDeclarons 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’;import { PersonneComponent } from ’./composants/personne/personne.component’;import { AuthComponent } from ’./composants/auth/auth.component’;import { AuthInterceptor } from ’./services/auth.interceptor’;
@NgModule({declarations: [
AppComponent,PersonneComponent,AuthComponent
],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
© Achref EL MOUELHI ©
HttpInterceptor
Demandons a l’intercepteur de modifier toutes les requetes en ajoutant le token sauf pourcelle qui demande le jeton (a 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 {
constructor() { }
intercept(request: HttpRequest<unknown>, next: HttpHandler):Observable<HttpEvent<unknown>> {if (request.url !== ’http://localhost:8000/authentication_token’) {
const token = localStorage.getItem(’token’);request = request.clone({setHeaders: {Authorization: ’Bearer ’ + token
}});
}return next.handle(request);
}}
H & H: Research and Training 21 / 30
© Achref EL MOUELHI ©
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’
})export class PersonneService {
private url = ’http://localhost:8000/ws/personnes’;
constructor(private http: HttpClient) { }
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
© Achref EL MOUELHI ©
Guard CanActivate
Angular
Pour notre exemple, on va creer une garde qui verifie si unutilisateur est authentifie avant de charger certaines routes
ng g g guards/auth
Dans le menu qui s’affiche
Pointer sur CanActivate
Puis cliquer une premiere fois sur espace et une deuxieme surentree
H & H: Research and Training 23 / 30
© Achref EL MOUELHI ©
Guard CanActivate
Angular
Pour notre exemple, on va creer une garde qui verifie si unutilisateur est authentifie avant de charger certaines routes
ng g g guards/auth
Dans le menu qui s’affiche
Pointer sur CanActivate
Puis cliquer une premiere fois sur espace et une deuxieme surentree
H & H: Research and Training 23 / 30
© Achref EL MOUELHI ©
Guard CanActivate
Angular
On peut aussi preciser dans la commande l’interface aimplementer
ng g g guards/auth --implements CanActivate
Le resultatCREATE 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
© Achref EL MOUELHI ©
Guard CanActivate
Angular
On peut aussi preciser dans la commande l’interface aimplementer
ng g g guards/auth --implements CanActivate
Le resultatCREATE 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
© Achref EL MOUELHI ©
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’
})export class AuthGuard implements CanActivate {canActivate(
next: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<boolean | UrlTree> |
Promise<boolean | UrlTree> | boolean | UrlTree {return true;
}}
ActivatedRouteSnapshot : contient des informations comme les parametres envoyespour la route demandee...
RouterStateSnapshot :contient des informations comme l’URL de la route demandee
La methode canActivate ne fait aucun controle car elle retourne toujours true
H & H: Research and Training 25 / 30
© Achref EL MOUELHI ©
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’
})export class AuthGuard implements CanActivate {canActivate(
next: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<boolean | UrlTree> |
Promise<boolean | UrlTree> | boolean | UrlTree {return true;
}}
ActivatedRouteSnapshot : contient des informations comme les parametres envoyespour la route demandee...
RouterStateSnapshot :contient des informations comme l’URL de la route demandee
La methode canActivate ne fait aucun controle car elle retourne toujours true
H & H: Research and Training 25 / 30
© Achref EL MOUELHI ©
Guard CanActivate
AngularModifions 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’
})export class AuthGuard implements CanActivate {
constructor(private router: Router) { }
canActivate(next: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> |
boolean | UrlTree {
const token = localStorage.getItem(’token’);if (token) {
return true;}else {
this.router.navigateByUrl(’/auth’);return false;
}}
}
H & H: Research and Training 26 / 30
© Achref EL MOUELHI ©
Guard CanActivate
Associons AuthGuard aux differentes 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 { AuthComponent } from ’./composants/auth/auth.component’;import { AuthGuard } from ’./services/auth.guard’;
const routes: Routes = [{ path: ’auth’, component: AuthComponent },{ path: ’editPersonne/:id’, component: EditPersonneComponent,
canActivate: [AuthGuard] },{ path: ’personne’, component: PersonneComponent, canActivate: [
AuthGuard] },];
@NgModule({imports: [RouterModule.forRoot(routes)],exports: [RouterModule]
})export class AppRoutingModule { }
H & H: Research and Training 27 / 30
© Achref EL MOUELHI ©
Guard CanActivate
Angular
Pour eviter les repetitions 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’;import { AuthGuard } from ’./services/auth.guard’;
const routes: Routes = [
{ path: ’auth’, component: AuthComponent },{path: ’’, canActivate: [AuthGuard], children: [
{ path: ’editPersonne/:id’, component: EditPersonneComponent },{ path: ’personne’, component: PersonneComponent }
]}];
@NgModule({imports: [RouterModule.forRoot(routes)],exports: [RouterModule]
})export class AppRoutingModule { }
H & H: Research and Training 28 / 30
© Achref EL MOUELHI ©
Deconnexion
Angular
Pour se deconnecter, il suffit de
supprimer le token du localStorage
rediriger l’utilisateur vers la page d’authentification
H & H: Research and Training 29 / 30
© Achref EL MOUELHI ©
Deconnexion
Angular
Exercice
Dans le composant app-component, on veut afficher
un bouton deconnexion si un jeton est stocke dans leWebStorage
un lien connexion qui redirige vers la page d’authentificationsinon.
H & H: Research and Training 30 / 30