> ## Documentation Index
> Fetch the complete documentation index at: https://translations.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> La mise en œuvre Angular 2 du SPA pour le scénario d’architecture SPA + API

# Mise en œuvre Angular 2 du SPA (SPAs + API)

Ce document fait partie du scénario d’architecture SPA + API et explique comment mettre en œuvre le SPA avec Angular 2. Veuillez consulter le scénario pour en savoir plus sur la solution mise en œuvre.

Le code source complet de la mise en œuvre Angular 2 du SPA se trouve dans [ce dépôt GitHub](https://github.com/auth0-samples/auth0-pnp-exampleco-timesheets/tree/master/timesheets-spa/angular).

<div id="step-1-configuration">
  ## Étape 1. Configuration
</div>

Votre application aura besoin de certaines informations de configuration. Avant de poursuivre la mise en œuvre, créez une interface `AuthConfig` qui contiendra différentes valeurs de configuration. Placez cette interface dans un fichier appelé `auth0-variables.ts`.

```ts lines theme={null}
interface AuthConfig {
  clientID: string;
  domain: string;
  callbackURL: string;
  apiUrl: string;
}

export const AUTH_CONFIG: AuthConfig = {
  clientID: '',
  domain: '',
  callbackURL: 'http://localhost:4200/callback',
  apiUrl: ''
};
```

<div id="step-2-authorize-the-user">
  ## Étape 2. Autoriser l’utilisateur
</div>

<div id="create-an-authorization-service">
  ### Créer un service d’autorisation
</div>

La meilleure façon de gérer et de coordonner les tâches nécessaires à l’authentification de l’utilisateur est de créer un service réutilisable. Une fois ce service en place, vous pourrez appeler ses méthodes partout dans votre application. Vous pouvez créer dans ce service une instance de l’objet `WebAuth` de [auth0.js](/docs/fr-ca/libraries/auth0js).

```jsx lines expandable theme={null}
import { Injectable } from '@angular/core';
import { AUTH_CONFIG } from './auth0-variables';
import { Router } from '@angular/router';
import 'rxjs/add/operator/filter';
import auth0 from 'auth0-js';

@Injectable()
export class AuthService {

  userProfile: any;
  requestedScopes: string = 'openid profile read:timesheets create:timesheets';

  auth0 = new auth0.WebAuth({
    clientID: AUTH_CONFIG.clientID,
    domain: AUTH_CONFIG.domain,
    responseType: 'token id_token',
    audience: AUTH_CONFIG.apiUrl,
    redirectUri: AUTH_CONFIG.callbackURL,
    scope: this.requestedScopes
  });

  constructor(public router: Router) {}

  public login(): void {
    this.auth0.authorize();
  }

  public handleAuthentication(): void {
    this.auth0.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        window.location.hash = '';
        this.setSession(authResult);
        this.router.navigate(['/home']);
      } else if (err) {
        this.router.navigate(['/home']);
        console.log(err);
        alert('Error: <%= "${err.error}" %>. Check the console for further details.');
      }
    });
  }

  private setSession(authResult): void {
    // Définir l'heure d'expiration du jeton d'accès
    const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());

    // Si une valeur est présente dans le paramètre scope de authResult,
    // l'utiliser pour définir les scopes dans la session de l'utilisateur. Sinon,
    // utiliser les scopes tels que demandés. Si aucun scope n'a été demandé,
    // ne rien définir
    const scopes = authResult.scope || this.requestedScopes || '';

    localStorage.setItem('access_token', authResult.accessToken);
    localStorage.setItem('id_token', authResult.idToken);
    localStorage.setItem('expires_at', expiresAt);
    localStorage.setItem('scopes', JSON.stringify(scopes));
  }

  public logout(): void {
    // Supprimer les jetons et l'heure d'expiration du localStorage
    localStorage.removeItem('access_token');
    localStorage.removeItem('id_token');
    localStorage.removeItem('expires_at');
    localStorage.removeItem('scopes');
    // Revenir à la route d'accueil
    this.router.navigate(['/']);
  }

  public isAuthenticated(): boolean {
    // Vérifier si l'heure actuelle est postérieure à
    // l'heure d'expiration du jeton d'accès
    const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
    return new Date().getTime() < expiresAt;
  }

  public userHasScopes(scopes: Array<string>): boolean {
    const grantedScopes = JSON.parse(localStorage.getItem('scopes')).split(' ');
    return scopes.every(scope => grantedScopes.includes(scope));
  }
}
```

Le service comprend plusieurs méthodes pour gérer l’authentification.

* **login** : appelle `authorize` de auth0.js, ce qui lance <Tooltip tip="Universal Login : votre application est redirigée vers Universal Login, hébergé sur le serveur d’autorisation d’Auth0, afin de vérifier l’identité d’un utilisateur." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=Universal+Login">Universal Login</Tooltip>
* **handleAuthentication** : recherche un résultat d’authentification dans le hachage de l’URL et le traite à l’aide de la méthode `parseHash` d’auth0.js
* **setSession** : définit le <Tooltip tip="Jeton d’accès : information d’autorisation, sous la forme d’une chaîne opaque ou d’un JWT, utilisée pour accéder à une API." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=Access+Token">jeton d’accès</Tooltip> de l’utilisateur, son <Tooltip tip="Jeton d’accès : information d’autorisation, sous la forme d’une chaîne opaque ou d’un JWT, utilisée pour accéder à une API." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=ID+Token">ID Token</Tooltip>, ainsi que l’heure à laquelle le jeton d’accès expirera
* **logout** : supprime les jetons de l’utilisateur du stockage du navigateur
  isAuthenticated: vérifie si l’heure d’expiration du jeton d’accès est passée

<div id="process-the-authentication-result">
  ### Traiter le résultat de l’authentification
</div>

Lorsqu’un utilisateur s’authentifie au moyen d’Universal Login, puis qu’il est redirigé vers votre application, ses informations d’authentification se trouvent dans un fragment de hachage de l’URL. La méthode `handleAuthentication` de `AuthService` est chargée de traiter le hachage.

Appelez `handleAuthentication` dans le composant racine de votre application afin que le fragment de hachage d’authentification puisse être traité au premier chargement de l’application après la redirection de l’utilisateur.

```javascript lines theme={null}
// src/app/app.component.ts

import { Component } from '@angular/core';
import { AuthService } from './auth/auth.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {

  constructor(public auth: AuthService) {
    auth.handleAuthentication();
  }
}
```

<div id="add-the-callback-component">
  ### Ajouter le composant Callback
</div>

L’utilisation de Universal Login signifie que les utilisateurs sont redirigés hors de votre application vers une page hébergée par Auth0. Une fois leur authentification réussie, ils sont renvoyés vers votre application, où une session côté client est créée pour eux.

Vous pouvez choisir de faire revenir les utilisateurs vers n’importe quelle URL de votre application; toutefois, il est recommandé de créer une route de rappel dédiée qui servira de point central vers lequel l’utilisateur sera redirigé après une authentification réussie. Le fait d’avoir une seule route de rappel présente deux grands avantages :

* Cela évite d’avoir à mettre sur liste d’autorisation plusieurs URL de rappel (parfois même inconnues)
* Cela permet d’afficher un indicateur de chargement pendant que votre application crée la session côté client de l’utilisateur

Créez un composant nommé `CallbackComponent` et ajoutez-y un indicateur de chargement.

```jsx lines theme={null}
<!-- app/callback/callback.html -->

<div class="loading">
  <img src="/assets/loading.svg" alt="loading">
</div>
```

Cet exemple suppose qu’un indicateur de chargement est disponible dans le répertoire `assets`. Consultez l’exemple à télécharger pour en voir une démonstration.

Après l’authentification, les utilisateurs seront brièvement redirigés vers la route `/callback`, où un indicateur de chargement s’affichera. Pendant ce temps, leur session côté client sera établie, puis ils seront redirigés vers la route `/home`.

<div id="step-3-get-the-user-profile">
  ## Étape 3. Obtenir le profil de l’utilisateur
</div>

<Card title="Extraire des renseignements du jeton">
  Cette section explique comment récupérer les renseignements de l’utilisateur à l’aide du jeton d’accès et du [point de terminaison /userinfo](https://auth0.com/docs/api/authentication#get-user-info). Vous pouvez aussi simplement décoder le ID Token [à l’aide d’une bibliothèque](https://jwt.io/#libraries-io) (assurez-vous d’abord de le valider). Le résultat sera le même. Si vous avez besoin de renseignements supplémentaires sur l’utilisateur, envisagez d’utiliser [notre Management API](https://auth0.com/docs/api/management/v2#!/Users/get_users_by_id).
</Card>

Pour obtenir le profil de l’utilisateur, mettez à jour la classe `AuthService` existante. Ajoutez une fonction `getProfile` qui extraira le jeton d’accès de l’utilisateur du stockage local, puis le transmettra à la fonction `userInfo` pour récupérer les renseignements de l’utilisateur.

```javascript lines theme={null}
// Le code existant de la classe AuthService est omis dans cet exemple de code par souci de brièveté
@Injectable()
export class AuthService {
  public getProfile(cb): void {
    const accessToken = localStorage.getItem('access_token');
    if (!accessToken) {
      throw new Error('Access Token must exist to fetch profile');
    }

    const self = this;
    this.auth0.client.userInfo(accessToken, (err, profile) => {
      if (profile) {
        self.userProfile = profile;
      }
      cb(err, profile);
    });
  }
}
```

Vous pouvez maintenant simplement appeler cette fonction à partir de n’importe quel service depuis lequel vous souhaitez récupérer et afficher des renseignements sur l’utilisateur.

Par exemple, vous pouvez choisir de créer un nouveau composant pour afficher les renseignements du profil de l’utilisateur :

```jsx lines theme={null}
import { Component, OnInit } from '@angular/core';
import { AuthService } from './../auth/auth.service';

@Component({
  selector: 'app-profile',
  templateUrl: './profile.component.html',
  styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {

  profile: any;

  constructor(public auth: AuthService) { }

  ngOnInit() {
    if (this.auth.userProfile) {
      this.profile = this.auth.userProfile;
    } else {
      this.auth.getProfile((err, profile) => {
        this.profile = profile;
      });
    }
  }
}
```

Le gabarit de ce composant se présente comme suit :

```jsx lines theme={null}
<div class="panel panel-default profile-area">
  <div class="panel-heading">
    <h3>Profile</h3>
  </div>
  <div class="panel-body">
    <img src="{{profile?.picture}}" class="avatar" alt="avatar">
    <div>
      <label><i class="glyphicon glyphicon-user"></i> Nickname</label>
      <h3 class="nickname">{{ profile?.nickname }}</h3>
    </div>
    <pre class="full-profile">{{ profile | json }}</pre>
  </div>
</div>
```

<div id="step-4-display-ui-elements-conditionally-based-on-scope">
  ## Étape 4. Afficher les éléments de l’UI de façon conditionnelle selon la portée
</div>

Pendant le processus d’autorisation, nous avons déjà enregistré dans le stockage local les portées réellement accordées à l’utilisateur. Si le `scope` renvoyé dans `authResult` n’est pas vide, cela signifie que l’utilisateur a reçu un ensemble de portées différent de celui demandé au départ; nous devons donc utiliser `authResult.scope` pour déterminer les portées qui lui ont été accordées.

Si le `scope` renvoyé dans `authResult` est vide, cela signifie que l’utilisateur a obtenu toutes les portées demandées; nous pouvons donc utiliser les portées demandées pour déterminer celles qui lui ont été accordées.

Voici le code que nous avons écrit plus tôt pour la fonction `setSession` qui effectue cette vérification :

```jsx lines theme={null}
private setSession(authResult): void {
  // Définir l'heure d'expiration de l'access token
  const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());

  // S'il y a une valeur dans le paramètre `scope` de authResult,
  // l'utiliser pour définir les scopes dans la session de l'utilisateur. Sinon,
  // utiliser les scopes tels que demandés. Si aucun scope n'a été demandé,
  // laisser vide
  const scopes = authResult.scope || this.requestedScopes || '';

  localStorage.setItem('access_token', authResult.accessToken);
  localStorage.setItem('id_token', authResult.idToken);
  localStorage.setItem('expires_at', expiresAt);
  localStorage.setItem('scopes', JSON.stringify(scopes));
  this.scheduleRenewal();
}
```

Nous devons ensuite ajouter à la classe `AuthService` une fonction permettant de déterminer si un utilisateur a reçu un scope précis :

```jsx lines theme={null}
@Injectable()
export class AuthService {
  // certains éléments du code ont été omis par souci de concision

  public userHasScopes(scopes: Array<string>): boolean {
    const grantedScopes = JSON.parse(localStorage.getItem('scopes')).split(' ');
    return scopes.every(scope => grantedScopes.includes(scope));
  }
}
```

Vous pouvez appeler cette méthode pour déterminer si nous devons afficher ou non un élément précis de l’interface utilisateur. Par exemple, nous voulons afficher le lien **Approve Timesheets** seulement si l’utilisateur possède la portée `approve:timesheets`. Notez que, dans le code ci-dessous, nous avons ajouté un appel à la fonction `userHasScopes` pour déterminer si ce lien doit être affiché ou non.

```xml lines theme={null}
<nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <a class="navbar-brand" href="#">Timesheet System</a>
    </div>
    <div class="navbar-collapse collapse">
      <ul class="nav navbar-nav">
        <li><a routerLink="/">Home</a></li>
        <li><a *ngIf="auth.isAuthenticated()" routerLink="/profile">My Profile</a></li>
        <li><a *ngIf="auth.isAuthenticated()" routerLink="/timesheets">My Timesheets</a></li>
        <li><a *ngIf="auth.isAuthenticated() && auth.userHasScopes(['approve:timesheets'])" routerLink="/approval">Approve Timesheets</a></li>
      </ul>
      <ul class="nav navbar-nav navbar-right">
        <li><a *ngIf="!auth.isAuthenticated()" href="javascript:void(0)" (click)="auth.login()">Log In</a></li>
        <li><a *ngIf="auth.isAuthenticated()" href="javascript:void(0)" (click)="auth.logout()">Log Out</a></li>
      </ul>
    </div>
  </div>
</nav>

<main class="container">
  <router-outlet></router-outlet>
</main>
```

<div id="protect-a-route">
  ### Protéger une route
</div>

Nous devrions aussi protéger une route afin d’empêcher un utilisateur d’y accéder s’il ne s’est pas vu accorder les `scopes` appropriés. Pour ce faire, nous pouvons ajouter une nouvelle classe de service `ScopeGuardService` :

```jsx lines theme={null}
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { AuthService } from './auth.service';

@Injectable()
export class ScopeGuardService implements CanActivate {

  constructor(public auth: AuthService, public router: Router) {}

  canActivate(route: ActivatedRouteSnapshot): boolean {

    const scopes = (route.data as any).expectedScopes;

    if (!this.auth.isAuthenticated() || !this.auth.userHasScopes(scopes)) {
      this.router.navigate(['']);
      return false;
    }
    return true;
  }

}
```

Servez-vous-en ensuite lors de la configuration des routes pour déterminer si une route peut être activée. Remarquez l’utilisation du nouveau `ScopeGuardService` dans la définition de la route `approval` ci-dessous :

```javascript lines theme={null}
// app.routes.ts

import { Routes, CanActivate } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { ProfileComponent } from './profile/profile.component';
import { CallbackComponent } from './callback/callback.component';
import { AuthGuardService as AuthGuard } from './auth/auth-guard.service';
import { ScopeGuardService as ScopeGuard } from './auth/scope-guard.service';
import { TimesheetListComponent } from './timesheet-list/timesheet-list.component';
import { TimesheetAddComponent } from './timesheet-add/timesheet-add.component';
import { ApprovalComponent } from './approval/approval.component';

export const ROUTES: Routes = [
  { path: '', component: HomeComponent },
  { path: 'profile', component: ProfileComponent, canActivate: [AuthGuard] },
  { path: 'callback', component: CallbackComponent },
  { path: 'timesheets/add', component: TimesheetAddComponent, canActivate: [AuthGuard] },
  { path: 'timesheets', component: TimesheetListComponent, canActivate: [AuthGuard] },
  { path: 'approval', component: ApprovalComponent, canActivate: [ScopeGuard], data: { expectedScopes: ['approve:timesheets']} },
  { path: '**', redirectTo: '' }
];
```

<div id="step-5-call-the-api">
  ## Étape 5. Appeler l’API
</div>

Le module [angular2-jwt](https://github.com/auth0/angular2-jwt) peut être utilisé pour joindre automatiquement des <Tooltip tip="JSON Web Token (JWT) : format standard de jeton ID (et souvent de jeton d’accès) utilisé pour représenter de façon sécurisée des claims entre deux parties." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=JSON+Web+Tokens">JSON Web Tokens</Tooltip> aux requêtes adressées à votre API. Pour ce faire, il fournit une classe `AuthHttp` qui encapsule la classe `Http` d’Angular.

Installez `angular2-jwt` :

```bash lines theme={null}
# installation avec npm
npm install --save angular2-jwt

# installation avec yarn
yarn add angular2-jwt
```

Créez une fonction de fabrique contenant quelques valeurs de configuration pour `angular2-jwt`, puis ajoutez-la au tableau `providers` de l’`@NgModule` de votre application. La fonction de fabrique doit inclure une fonction `tokenGetter` qui récupère l’`access_token` à partir du stockage local.

```jsx lines theme={null}
import { Http, RequestOptions } from '@angular/http';
import { AuthHttp, AuthConfig } from 'angular2-jwt';

export function authHttpServiceFactory(http: Http, options: RequestOptions) {
  return new AuthHttp(new AuthConfig({
    tokenGetter: (() => localStorage.getItem('access_token'))
  }), http, options);
}

@NgModule({
  declarations: [...],
  imports: [...],
  providers: [
    AuthService,
    {
      provide: AuthHttp,
      useFactory: authHttpServiceFactory,
      deps: [Http, RequestOptions]
    }
  ],
  bootstrap: [...]
})
```

Une fois `angular2-jwt` configuré, vous pouvez utiliser la classe `AuthHttp` pour effectuer des requêtes sécurisées vers votre API depuis n’importe où dans l’application. Pour ce faire, injectez `AuthHttp` dans tout composant ou service qui en a besoin et utilisez-le comme vous le feriez avec la classe `Http` standard d’Angular.

```jsx lines theme={null}
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { AuthHttp } from 'angular2-jwt';
import 'rxjs/add/operator/map';
import { NewTimesheetModel } from '../models/new-timesheet-model';

@Injectable()
export class TimesheetsService {

  constructor(public authHttp: AuthHttp) { }

  addTimesheet(model: NewTimesheetModel) {
    return this.authHttp.post('http://localhost:8080/timesheets', JSON.stringify(model));
  }

  getAllTimesheets() {
    return this.authHttp.get('http://localhost:8080/timesheets')
      .map(res => res.json())
  }
}
```

<div id="step-6-renew-the-access-token">
  ## Étape 6. Renouveler le jeton d’accès
</div>

Le renouvellement du jeton d’accès de l’utilisateur nécessite une mise à jour de la SPA Angular. Ajoutez une méthode à `AuthService` qui appelle la méthode `checkSession` d’auth0.js. Si le renouvellement réussit, utilisez la méthode `setSession` existante pour enregistrer les nouveaux jetons dans le stockage local.

```ts lines theme={null}
public renewToken() {
  this.auth0.checkSession({
    audience: AUTH_CONFIG.apiUrl
  }, (err, result) => {
    if (!err) {
      this.setSession(result);
    }
  });
}
```

Dans la classe `AuthService`, ajoutez une méthode appelée `scheduleRenewal` pour définir le moment où l’authentification doit être renouvelée en silence. Dans l’exemple ci-dessous, elle est configurée pour s’exécuter 30 secondes avant l’expiration réelle du jeton. Ajoutez également une méthode appelée `unscheduleRenewal` pour annuler l’abonnement à l’Observable.

```ts lines expandable theme={null}
public scheduleRenewal() {
  if (!this.isAuthenticated()) return;

  const expiresAt = JSON.parse(window.localStorage.getItem('expires_at'));

  const source = Observable.of(expiresAt).flatMap(
    expiresAt => {

      const now = Date.now();

      // Utiliser le délai dans un minuteur pour
      // exécuter l'actualisation au bon moment
      var refreshAt = expiresAt - (1000 * 30); // Actualiser 30 secondes avant l'expiration
      return Observable.timer(Math.max(1, refreshAt - now));
    });

  // Une fois le délai ci-dessus écoulé,
  // obtenir un nouveau JWT et planifier
  // des actualisations supplémentaires
  this.refreshSubscription = source.subscribe(() => {
    this.renewToken();
  });
}

public unscheduleRenewal() {
  if (!this.refreshSubscription) return;
  this.refreshSubscription.unsubscribe();
}
```

Enfin, vous devez démarrer le renouvellement planifié. Pour ce faire, appelez `scheduleRenewal` dans votre `AppComponent`, afin qu’il s’exécute au chargement de la page. Cela se produira après chaque flux d’authentification, soit lorsque l’utilisateur se connecte explicitement, soit lors d’une authentification silencieuse.

<Info>
  ### Rotation des jetons d’actualisation

  Les récentes avancées des contrôles de confidentialité dans les navigateurs nuisent à l’expérience utilisateur en bloquant l’accès aux cookies tiers. Auth0 recommande d’utiliser la [rotation des jetons d’actualisation](/docs/fr-ca/secure/tokens/refresh-tokens/refresh-token-rotation), qui offre une méthode sécurisée pour utiliser des jetons d’actualisation dans les SPA, tout en donnant aux utilisateurs finaux un accès fluide aux ressources, sans les perturbations de l’expérience utilisateur causées par des technologies de confidentialité des navigateurs comme ITP.
</Info>
