• 8 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/31/22

Contrôlez les accès avec les guards

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 :

<div class="form-card">
  <h2>Connexion</h2>
  <div class="form-group">
    <label for="email">Email</label>
    <input type="text" id="email">
  </div>
  <div class="form-group">
    <label for="password">Mot de passe</label>
    <input type="password" id="password">
  </div>
  <div class="action-buttons">
    <button (click)="onLogin()">Connexion</button>
  </div>
</div>

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 :

Snapface  Une page de connexion toute blanche avec champs Email, Mot de passe et bouton connexion
La page login

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()  retourne  true  pour laisser continuer la navigation, et  false  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... 

Example of certificate of achievement
Example of certificate of achievement