L'authentification
L'application développée en backend
nécessite une authentification pour fonctionner. Les routes utilisées auront besoin d'un jeton valide pour y accéder.
Ce jeton va s'obtenir en envoyant un couple email
+ password
sur une route spécifique avec la méthode POST
. Ce jeton sera valide pendant 24h.
Il faudra ensuite l'envoyer à chaque requête vers le serveur pour prouver que l'utilisateur est connecté.
L'URL pour récupérer un jeton est : http://localhost:3000/users/login
Nous allons voir comment gérer ceci dans une application Angular.
Angular Route Guard
Angular met à disposition des développeurs plusieurs méthodes pour sécuriser les routes de l'application. Ces interfaces sont :
CanActivate
: pour contrôler l'accès à une route.CanActivateChild
: similaire à la précédente, mais s'applique sur l'ensemble des routes filles de la route ciblée.CanLoad
: pour la mise en place dulazy loading
CanDeactivate
: pour empêcher un utilisateur de quitter une routeResolve
: pour s'assurer que toutes les données nécessaires sont bien présentes
Les services qui implémentent ces interfaces afin de déterminer si une partie de l'application doit être accessible ou non à un utilisateur est appelé Guard
dans l'univers angular.
Nous allons donc créer un service et implementer l'interface CanActivate
disponible dans le package @angular/router
.
L'interface CanActivate
oblige à définir la méthode canActivate(): boolean
. Cette méthode doit renvoyer true
si l'utilisateur est autorisé à passer, false
sans les autres cas. Exemple :
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private myService: MyService) { }
canActivate(): boolean {
if (this.myService.userAuthorized) {
return true;
}
return false;
}
}
Ensuite, dans le module de routing, il faut ajouter sur la route protégée la clè canActivate
et lui passer en paramètre le service guard
précédemment créé :
const routes: Routes = [
{ path: 'my-component', component: MyComponent, canActivate: [AuthGuard] },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Attention : le paramètre canActivate
de la route prend un tableau de guards
comme paramètre.
TP : Développement d'un module de connexion
Dans cette partie nous allons développer le module LoginModule
qui permettra à un utilisateur de se connecter sur l'application. Lors de la connexion, nous allons récupérer un token
qui sera stocké. Nous ajouterons également une route protégée dans le module principal simplement pour le test.
- Créer un module
LoginModule
- Créer le model
UserLogin
avec comme propriétéemail
etpassword
qui représente les données à envoyer à la route login - Créer le model
UserToken
avec comme propriétéuserId
ettoken
qui représente la réponse de la routelogin
coté backend - Créer un service
LoginService
avec une propriétéuserToken
et une méthodelogin
- Créer un component
LoginComponent
qui sera le formulaire de connexion de l'utilisateur avec deux champsemail
etpassword
. Le formulaire doit faire appel à la méthodelogin
deLoginService
- La méthode
login
deLoginService
doit envoyer une requête au serveur sur/users/login
afin de récupérer untoken
. La réponse de la requête doit être stockée dans la propriétéuserToken
du service. - Créer un service
Guard
pour vérifier que l'utilisateur à bien récupéré untoken
. Si l'utilisateur n'a pas detoken
, le rediriger le sur le formulaire de login et faire apparaître une notificationtoastr
pour l'en informer - Créer un fichier de
routing
pour le moduleLoginModule
avec comme cheminlogin
pour aller sur le componentlogin
- Ajouter un component
MissionListComponent
qui affiche juste un titreh1
. Ajouter ce component dans le module principalAppModule
- Ajouter une route
mission-list
dans le fichier de routing principalAppRoutingModule
qui redirige versMissionListComponent
. Ce component doit être protégé par le serviceGuard
. - Dans la vue de
AppComponent
mettre uniquement le<router-outlet></router-outlet>
- N'oubliez pas d'importer votre module
LoginModule
dans le module principal
Résultat attendu :
Le Local Storage
Pour le moment notre authentification n'est pas persistante, cela signifie que si l'utilisateur recharge sa page il devra se reconnecter.
Pour faire en sorte que l'utilisateur ne se reconnecte pas à chaque rechargement de l'application nous allons devoir stocker le token
de manière persistante.
Pour cela nous allons utiliser une fonctionnalité disponible dans les navigateurs : le LocalStorage. Il permet aux développeurs de stocker des informations dans le navigateur. Au rechargement de la page, l'application pourra alors récupérer les informations qui y sont stockées.
Pour l'utiliser vous devez appeler la variable localStorage
disponible n'importe où dans l'application. Les méthodes pour gérer le localStorage sont :
setItem(key: string, value: string)
pour stocker une variablegetItem(key: string)
pour récupérer une variableremoveItem(key: string)
pour supprimer une variable
Attention : le localstorage ne peut stocker que des chaînes de caractères et pas des variables complexes comme des objets. Pour stocker des objets il faudra alors les sérialiser.
Exemple :
// Stockage de la variable userToken
const userTokenSerialized: string = JSON.stringify(this.userToken); // sérialisation
localStorage.setItem('userToken', userTokenSerialized);
// Récupération de la variable userToken
const userTokenLocalStorage: string = localStorage.getItem('userToken');
const userToken = JSON.parse(userTokenLocalStorage); // désérialisation
// Suppression de la variable userToken
localStorage.removeItem('userToken');
TP : Persistance du token
- Ajouter une méthode
saveTokenToLocalStorage
dans le serviceLoginService
qui stockera l'objetuserToken
dans le Local Storage - Ajouter une méthode
restoreTokenFromLocalStorage
dans le serviceLoginService
pour restaurer l'objetuserToken
depuis le Local Storage - À la connexion de l'utilisateur, n'oubliez pas d'appeler la fonction pour stocker le
userToken
dans le Local Storage - Dans le component principal
AppComponent
, implémenter l'interfaceOnInit
et ajouter la méthodengOnInit()
- La méthode
ngOnInit()
sera appelée au démarrage de l'application. Pour restaurer le token vous devez appeler la méthoderestoreTokenFromLocalStorage()
du serviceLoginService
- Ajouter une méthode
clearTokenFromLocalStorage()
dans le serviceLoginService
qui va supprimer l'objetuserToken
depuis le Local Storage. Nous utiliserons cette méthode plus tard dans le TP.
Vous pouvez alors constater qu'une fois connecté, le rechargement de la page mission-list
ne redirige pas sur la page login
. Le token est bien restauré au démarrage.
TP : Http Interceptor
Pour prouver au serveur que nous avons l'autorisation d'utiliser l'application nous devons envoyer à chaque requête HTTP le token dans le header Authorization
sous la forme Authorization: Bearer <token>
. Pour faire cela Angular met à notre disposition un mécanisme appelé HttpInterceptor
qui va modifier la requête pour nous à chaque appel de HttpClient
.
Pour l'utiliser nous devons ajouter un nouveau service qui implémente l'interface HttpInterceptor
qui oblige l'ajout d'une méthode intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
. Cette méthode sera executée à chaque fois qu'une requête est envoyé. C'est ici que nous allons ajouter notre token.
Ajouter le service TokenInterceptorService
, comme ceci :
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
import { LoginService } from './login.service';
@Injectable()
export class TokenInterceptorService implements HttpInterceptor {
constructor(private loginService: LoginService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const userToken = this.loginService.userToken;
let newHeaders = req.headers;
if (userToken) {
newHeaders = newHeaders.append('Authorization', `Bearer ${userToken.token}`);
}
return next.handle(req.clone({headers: newHeaders}));
}
}
Il faut ensuite déclarer cet intercepteur dans notre LoginModule
dans la partie providers
, comme ceci :
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { TokenInterceptorService } from './token-interceptor.service';
...
providers: [
LoginService,
AuthGuard,
{ provide: HTTP_INTERCEPTORS, useClass: TokenInterceptorService, multi: true }
]
...
Dorénavant, à chaque requête HTTP que notre application va lancer, il y aura un en-tête contenant le token de l'utilisateur si celui-ci s'est connecté.