Les applications web aujourd'hui comportent presque toutes un système d'authentification. Pour nous, dans le frontend, cela signifie qu'il y aura certaines routes de l'application auxquelles les utilisateurs non authentifiés ne doivent pas pouvoir accéder.
Angular nous propose un outil pour protéger ces routes : les guards.
Les guards sont implémentés au niveau du routing. Quand un utilisateur essaie d'accéder à une route protégée, le guard est appelé et vérifie l'état d'authentification de l'utilisateur. S'il est authentifié, on le laisse passer ; sinon, on implémente la logique de refus que l'on souhaite appliquer. Généralement, on redirige l'utilisateur vers la page de login pour lui permettre de se connecter.
L'application Snapface ne comporte pas de système d'authentification. Vous allez donc implémenter un "faux" système (qui ne vérifie pas vraiment les identifiants entrés), qui permettra la mise en place d'un guard pour les routes facesnaps/
.
C'est parti !
Authentifiez vos utilisateurs
Je vous propose d'implémenter un feature module pour l'authentification (AuthModule) :
il ne sera pas lazy loaded, mais il s'occupera de son propre routing :
il aura donc un AuthRoutingModule mais ne sera pas appelé avec loadChildren : ses routes seront simplement enregistrées avec RouterModule.forChild() et AuthModule sera directement importé dans AppModule;
il aura un seul component, LoginComponent, qui comportera un formulaire (qui ne fait rien) et un bouton "Login" qui va initialiser le faux token dans AuthService :
il faudra modifier l'implémentation actuelle du service pour que le token ne soit pas défini par défaut,
il y aura également une méthode dans AuthService pour login (initialiser le token),
une fois le token initialisé, l'utilisateur doit être redirigé vers
facesnaps
.
Une fois tout cela implémenté, tout sera prêt pour implémenter un guard pour protéger les routes facesnaps
.
À vous de jouer !
Je vous propose d'essayer l'implémentation vous-même : prenez votre temps, allez le plus loin possible, n'hésitez pas à regarder en arrière les chapitres des cours qui pourront vous aider – si vous y arrivez, tant mieux, sinon, c'est pas grave, la solution se trouve ci-dessous !
Suivez mon approche
Générez AuthModule
D'abord, vous pouvez générer AuthModule avec le CLI :
ng g m auth
Pour générer LoginComponent directement dans auth/components/login
, vous pouvez utiliser le CLI :
ng g c auth/components/login
Passez au service et implémentez le comportement décrit :
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private token!: string;
login() {
this.token = 'MyFakeToken';
}
getToken(): string {
return this.token;
}
}
Ensuite, il faut créer et brancher le routing.
Créez auth/auth-routing.module.ts
et implémentez-y un module de routing :
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './components/login/login.component';
const routes: Routes = [
{ path: 'auth/login', component: LoginComponent }
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [
RouterModule
]
})
export class AuthRoutingModule {}
Pour tout brancher, importez AuthRoutingModule dans AuthModule, puis AuthModule dans AppModule !
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoginComponent } from './components/login/login.component';
import { AuthRoutingModule } from './auth-routing.module';
@NgModule({
declarations: [
LoginComponent
],
imports: [
CommonModule,
AuthRoutingModule
]
})
export class AuthModule { }
@NgModule({
// ...
imports: [
BrowserModule,
AppRoutingModule,
CoreModule,
LandingPageModule,
AuthModule
],
// ...
})
export class AppModule {}
Maintenant, vous pouvez naviguer vers http://localhost:4200/auth/login et voir login works!
.
Implémentez LoginComponent
LoginComponent aura besoin de AuthService et du Router, donc injectez-les et créez une méthode onLogin()
pour réagir aux clics sur le bouton de connexion :
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../../core/services/auth.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
constructor(private auth: AuthService,
private router: Router) { }
ngOnInit(): void {
}
onLogin() {
this.auth.login();
this.router.navigateByUrl('/facesnaps');
}
}
Niveau template, je vous propose une structure similaire à NewFaceSnapComponent :
class="form-card"
Connexion
class="form-group"
for="email"Email
type="text" id="email"
class="form-group"
for="password"Mot de passe
type="password" id="password"
class="action-buttons"
(click)="onLogin()"Connexion
Tout en "volant" les styles nécessaires à NewFaceSnapComponent également :
.form-card {
box-sizing: border-box;
width: 50%;
margin: 20px auto;
padding: 10px 30px;
box-shadow: lightgray 4px 4px 20px;
}
.form-group {
margin: 10px auto;
width: 80%;
display: flex;
align-items: center;
justify-content: space-between;
}
input, textarea {
width: 50%;
}
.action-buttons {
width: 100%;
}
button {
display: block;
margin: 20px auto;
}
Si vous naviguez à la page de login, vous voyez :
Et si vous cliquez sur Connexion, vous êtes bien redirigé.
Tout est maintenant en place pour protéger les routes avec un guard !
Protégez les routes
Un guard est une classe qui implémente l'interface CanActivate. La classe doit être @Injectable()
et providedIn: 'root'
comme un service, et doit contenir une méthode canActivate()
. Dans un sous-dossier guards
du dossier core
, créez auth.guard.ts
:
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { AuthService } from '../services/auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService,
private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const token = this.auth.getToken();
if (token) {
return true;
} else {
this.router.navigateByUrl('/auth/login');
return false;
}
}
}
Si le token existe, la méthode canActivate()
retourne true
, et la navigation continue. Inversement, si le token n'existe pas, canActivate()
lance la redirection et retourne false
, empêchant la navigation directe.
Pour enregistrer ce guard, tout se passe dans FaceSnapsRoutingModule :
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SingleFaceSnapComponent } from './components/single-face-snap/single-face-snap.component';
import { FaceSnapListComponent } from './components/face-snap-list/face-snap-list.component';
import { NewFaceSnapComponent } from './components/new-face-snap/new-face-snap.component';
import { AuthGuard } from '../core/guards/auth.guard';
const routes: Routes = [
{ path: 'create', component: NewFaceSnapComponent, canActivate: [AuthGuard] },
{ path: ':id', component: SingleFaceSnapComponent, canActivate: [AuthGuard] },
{ path: '', component: FaceSnapListComponent, canActivate: [AuthGuard] },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class FaceSnapsRoutingModule {}
Avec l'enregistrement du guard, essayez de naviguer directement vers http://localhost:4200/facesnaps, et vous serez bien redirigé vers la page de login. En cliquant sur Connexion, vous serez redirigé correctement vers FaceSnaps.
Le guard est bien implémenté !
En résumé
Un guard est un outil de routing qui empêche des utilisateurs non autorisés d'accéder aux routes protégées de l'application ;
Un guard est une classe
@Injectable
qui implémente l'interface CanActivate ;La méthode
canActivate()
retournetrue
pour laisser continuer la navigation, etfalse
pour l'empêcher ;On passe ensuite le guard à la configuration de la route dans le tableau canActivate.
Qu'avons-nous appris dans cette partie du cours ?
Les modules permettent de séparer votre application en parties logiques et plus faciles à maintenir ;
Il y a trois types principaux de modules :
les feature modules qui encapsulent un feature de l'application ;
les core modules pour tout ce qui est déclaré ou importé une seule fois dans l'application ;
les shared modules pour tout ce qui est importé à plusieurs endroits de l'application.
Le lazy loading est une technique de routing qui génère un nouveau fichier JS dans l'application finale qui est chargé au besoin, améliorant les performances de l'application ;
Les guards permettent de protéger certaines routes de l'application frontend des utilisateurs non authentifiés.
À la fin du quiz qui suit ce chapitre, vous aurez terminé ce cours ! Vous avez tout ce qu'il vous faut pour commencer à développer avec le framework Angular.
Après le dernier quiz, je vous expliquerai comment poursuivre votre apprentissage du framework Angular...