Partage via


Tutoriel : Connecter les utilisateurs et appeler l’API Microsoft Graph à partir d’une application monopage (SPA) Angular à l’aide du flux de code d’authentification

Dans ce tutoriel, vous générez une application monopage (SPA) Angular qui connecte les utilisateurs et appelle l’API Microsoft Graph à l’aide du flux de code d’autorisation avec la clé de preuve pour l’échange de code (PKCE). L’application SPA que vous générez utilise la bibliothèque d’authentification MSAL (Microsoft Authentication Library) pour Angular v2.

Dans ce tutoriel, vous allez :

  • Enregistrez l'application dans le centre d'administration Microsoft Entra
  • Créer un projet angulaire avec npm.
  • Ajouter du code pour prendre en charge la connexion et la déconnexion des utilisateurs
  • Ajouter du code pour appeler l’API Microsoft Graph
  • Test de l'application

MSAL Angular v2 utilise le flux de code d’autorisation avec PKCE dans le navigateur, ce qui constitue une amélioration par rapprt à MSAL Angular v1, qui utilisait un flux d’octroi implicite. Nous vous recommandons d’utiliser le flux de code d’autorisation avec PKCE pour les applications monopages (SPA), car il est plus sécurisé que le flux implicite. MSAL Angular v2 ne prend PAS en charge le flux implicite.

Prérequis

  • Node.js pour l’exécution d’un serveur web local
  • Visual Studio Code ou un autre éditeur pour la modification des fichiers projet

Fonctionnement de l’exemple d’application

Diagramme montrant le flux de code d’autorisation dans une application monopage

L’exemple d’application créé dans ce tutoriel permet à une application monopage Angular d’interroger l’API Microsoft Graph ou une API web qui accepte des jetons émis par la plateforme d’identités Microsoft. Elle utilise la bibliothèque d’authentification MSAL (Microsoft Authentication Library) pour Angular v2, un wrapper de la bibliothèque MSAL.js v2. MSAL Angular permet aux applications Angular 9+ d’authentifier les utilisateurs en entreprise avec Microsoft Entra ID, ainsi que les utilisateurs qui ont des comptes Microsoft et des identités de réseaux sociaux tels que Facebook, Google et LinkedIn. La bibliothèque permet aussi aux applications d’accéder aux services cloud Microsoft et à Microsoft Graph.

Dans ce scénario, une fois qu’un utilisateur s’est connecté, un jeton d’accès est demandé et ajouté aux requêtes HTTP par le biais de l’en-tête d’autorisation. Les opérations d’acquisition et de renouvellement de jetons sont gérées par MSAL.

Bibliothèques

Ce tutoriel utilise les bibliothèques suivantes :

Bibliothèque Description
MSAL Angular Wrapper de la bibliothèque d’authentification Microsoft pour JavaScript Angular
Navigateur MSAL Package de navigateur de la bibliothèque d’authentification Microsoft pour JavaScript v2

Vous pouvez trouver le code source de toutes les bibliothèques MSAL.js dans le référentiel microsoft-authentication-library-for-js sur GitHub.

Obtenir l’exemple de code complet

Préférez-vous télécharger l’exemple de projet terminé pour ce tutoriel à la place ? Cloner le paramètre ms-identity-javascript-angular-spa

git clone https://github.com/Azure-Samples/ms-identity-javascript-angular-spa.git

Pour poursuivre le tutoriel et générer vous-même l’application, passez à la section suivante : Inscrire l’application et enregistrer les identificateurs.

Inscrire l’application et enregistrer les identificateurs

Conseil

Les étapes décrites dans cet article peuvent varier légèrement en fonction du portail de départ.

Pour terminer l’inscription, indiquez le nom de l’application, spécifiez les types de comptes pris en charge et ajoutez un URI de redirection. Une fois l’inscription terminée, le volet Vue d’ensemble de l’application affiche les identificateurs nécessaires dans le code source de l’application.

  1. Connectez-vous au centre d’administration de Microsoft Entra au minimum en tant que Développeur d’application.
  2. Si vous avez accès à plusieurs tenants, utilisez l’icône Paramètres dans le menu supérieur pour basculer vers le tenant dans lequel vous voulez inscrire l’application à partir du menu Répertoires + abonnements.
  3. Accédez à Identité>Applications>Inscriptions d’applications.
  4. Sélectionnez Nouvelle inscription.
  5. Entrez un Nom pour l’application, par exemple Angular-SPA-auth-code.
  6. Pour les Types de comptes pris en charge, sélectionnez Comptes dans cet annuaire organisationnel. Pour plus d’informations sur les différents types de comptes, sélectionnez l’option M’aider à choisir.
  7. Sous URI de redirection (facultatif), utilisez le menu déroulant pour sélectionner Application monopage (SPA), puis entrez http://localhost:4200 dans la zone de texte.
  8. Sélectionnez Inscription.
  9. Le Vue d’ensemble de l’application s’affiche une fois l’inscription terminée. Enregistrez l’ID du répertoire (locataire) et l’ID d’application (client) à utiliser dans le code source de votre application.

Créer votre projet

  1. Ouvrez Visual Studio Code.

  2. Sélectionnez Fichier>Ouvrir le dossier.... Accédez à et sélectionnez l’emplacement dans lequel vous souhaitez créer votre projet.

  3. Ouvrez un nouveau terminal en sélectionnant Terminal>Nouveau Terminal.

    1. Vous devrez peut-être changer de type de terminal. Sélectionnez la flèche vers le bas à côté de l’icône + dans le terminal, puis sélectionnez Invite de commandes.
  4. Exécutez les commandes suivantes pour créer un projet Angular nommé msal-angular-tutorial, installez les bibliothèques de composants Material Angular, MSAL Browser, MSAL Angular et générez des composants d’accueil et de profil.

    npm install -g @angular/cli
    ng new msal-angular-tutorial --routing=true --style=css --strict=false
    cd msal-angular-tutorial
    npm install @angular/material @angular/cdk
    npm install @azure/msal-browser @azure/msal-angular
    ng generate component home
    ng generate component profile
    

Configurer l’application et modifier l’interface utilisateur de base

  1. Ouvrez src/app/app.module.ts. Les modules MsalModule et MsalInterceptor doivent être ajoutés à imports avec la constante isIE. Vous ajouterez également les modules matériels. Remplacez l’intégralité du contenu du fichier par l’extrait de code suivant :

    import { BrowserModule } from "@angular/platform-browser";
    import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
    import { NgModule } from "@angular/core";
    
    import { MatButtonModule } from "@angular/material/button";
    import { MatToolbarModule } from "@angular/material/toolbar";
    import { MatListModule } from "@angular/material/list";
    
    import { AppRoutingModule } from "./app-routing.module";
    import { AppComponent } from "./app.component";
    import { HomeComponent } from "./home/home.component";
    import { ProfileComponent } from "./profile/profile.component";
    
    import { MsalModule, MsalRedirectComponent } from "@azure/msal-angular";
    import { PublicClientApplication } from "@azure/msal-browser";
    
    const isIE =
      window.navigator.userAgent.indexOf("MSIE ") > -1 ||
      window.navigator.userAgent.indexOf("Trident/") > -1;
    
    @NgModule({
      declarations: [AppComponent, HomeComponent, ProfileComponent],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        AppRoutingModule,
        MatButtonModule,
        MatToolbarModule,
        MatListModule,
        MsalModule.forRoot(
          new PublicClientApplication({
            auth: {
              clientId: "Enter_the_Application_Id_here", // Application (client) ID from the app registration
              authority:
                "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here", // The Azure cloud instance and the app's sign-in audience (tenant ID, common, organizations, or consumers)
              redirectUri: "Enter_the_Redirect_Uri_Here", // This is your redirect URI
            },
            cache: {
              cacheLocation: "localStorage",
              storeAuthStateInCookie: isIE, // Set to true for Internet Explorer 11
            },
          }),
          null,
          null
        ),
      ],
      providers: [],
      bootstrap: [AppComponent, MsalRedirectComponent],
    })
    export class AppModule {}
    
  2. Remplacez les valeurs suivantes par les valeurs obtenues à partir du centre d'administration Microsoft Entra. Pour plus d’informations sur les options configurables disponibles, consultez Initialiser les applications clientes.

    • clientId – Identificateur de l’application, également appelé client. Remplacez Enter_the_Application_Id_Here par la valeur du paramètre ID d’application (client) enregistrée précédemment depuis la page Vue d’ensemble de l’application inscrite.
    • authority – Il se compose de deux parties :
      • L’instance est le point de terminaison du fournisseur de cloud. Pour le cloud Azure principal ou mondial, entrez https://login.microsoftonline.com. Vérifiez les différents points de terminaison disponibles dans les clouds nationaux.
      • L’ID du locataire est l’identificateur du locataire dans lequel l’application est inscrite. Remplacez _Enter_the_Tenant_Info_Here par la valeur du paramètre ID d’application (locataire) enregistrée précédemment depuis la page Vue d’ensemble de l’application inscrite.
    • redirectUri - Emplacement vers lequel le serveur d’autorisation envoie l’utilisateur une fois que l’application a été autorisée et qu’un code d’autorisation ou un jeton d’accès lui a été attribué. Remplacez Enter_the_Redirect_Uri_Here par http://localhost:4200.
  3. Ouvrez src/app/app-routing.module.ts et ajoutez des itinéraires aux composants d’accueil et de profil. Remplacez l’intégralité du contenu du fichier par l’extrait de code suivant :

    import { NgModule } from "@angular/core";
    import { Routes, RouterModule } from "@angular/router";
    import { BrowserUtils } from "@azure/msal-browser";
    import { HomeComponent } from "./home/home.component";
    import { ProfileComponent } from "./profile/profile.component";
    
    const routes: Routes = [
      {
        path: "profile",
        component: ProfileComponent,
      },
      {
        path: "",
        component: HomeComponent,
      },
    ];
    
    const isIframe = window !== window.parent && !window.opener;
    
    @NgModule({
      imports: [
        RouterModule.forRoot(routes, {
          // Don't perform initial navigation in iframes or popups
          initialNavigation:
            !BrowserUtils.isInIframe() && !BrowserUtils.isInPopup()
              ? "enabledNonBlocking"
              : "disabled", // Set to enabledBlocking to use Angular Universal
        }),
      ],
      exports: [RouterModule],
    })
    export class AppRoutingModule {}
    
  4. Ouvrez src/app/app.component.html et remplacez le code existant par l’extrait de code suivant :

    <mat-toolbar color="primary">
      <a class="title" href="/">{{ title }}</a>
    
      <div class="toolbar-spacer"></div>
    
      <a mat-button [routerLink]="['profile']">Profile</a>
    
      <button mat-raised-button *ngIf="!loginDisplay" (click)="login()">Login</button>
    
    </mat-toolbar>
    <div class="container">
      <!--This is to avoid reload during acquireTokenSilent() because of hidden iframe -->
      <router-outlet *ngIf="!isIframe"></router-outlet>
    </div>
    
  5. Ouvrez src/style.css pour définir le CSS :

    @import "~@angular/material/prebuilt-themes/deeppurple-amber.css";
    
    html,
    body {
      height: 100%;
    }
    body {
      margin: 0;
      font-family: Roboto, "Helvetica Neue", sans-serif;
    }
    .container {
      margin: 1%;
    }
    
  6. Ouvrez src/app/app.component.css pour ajouter un style CSS à l’application :

    .toolbar-spacer {
      flex: 1 1 auto;
    }
    
    a.title {
      color: white;
    }
    

Se connecter à l’aide de fenêtres contextuelles

  1. Ouvrez src/app/app.component.ts et remplacez le contenu du fichier par l’extrait de code suivant pour connecter un utilisateur à l’aide d’une fenêtre contextuelle :

    import { MsalService } from '@azure/msal-angular';
    import { Component, OnInit } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit {
      title = 'msal-angular-tutorial';
      isIframe = false;
      loginDisplay = false;
    
      constructor(private authService: MsalService) { }
    
      ngOnInit() {
        this.isIframe = window !== window.parent && !window.opener;
      }
    
      login() {
        this.authService.loginPopup()
          .subscribe({
            next: (result) => {
              console.log(result);
              this.setLoginDisplay();
            },
            error: (error) => console.log(error)
          });
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    }
    

Se connecter à l’aide de redirections

  1. Mettez à jour src/app/app.module.ts pour démarrer MsalRedirectComponent. Il s’agit d’un composant de redirection dédié qui gère les redirections. Modifiez l’importation MsalModule et le démarrage AppComponent pour qu’ils ressemblent à l’extrait de code suivant :

    ...
    import { MsalModule, MsalRedirectComponent } from '@azure/msal-angular'; // Updated import
    ...
      bootstrap: [AppComponent, MsalRedirectComponent] // MsalRedirectComponent bootstrapped here
    ...
    
  2. Ouvrez src/index.html et remplacez l’intégralité du contenu du fichier par l’extrait de code suivant, qui ajoute le sélecteur <app-redirect> :

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <title>msal-angular-tutorial</title>
      <base href="/">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="icon" type="image/x-icon" href="favicon.ico">
    </head>
    <body>
      <app-root></app-root>
      <app-redirect></app-redirect>
    </body>
    </html>
    
  3. Ouvrez src/app/app.component.ts et remplacez le code par l’extrait de code suivant pour connecter un utilisateur à l’aide d’une redirection complète :

    import { MsalService } from '@azure/msal-angular';
    import { Component, OnInit } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit {
      title = 'msal-angular-tutorial';
      isIframe = false;
      loginDisplay = false;
    
      constructor(private authService: MsalService) { }
    
      ngOnInit() {
        this.isIframe = window !== window.parent && !window.opener;
      }
    
      login() {
        this.authService.loginRedirect();
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    }
    
  4. Ouvrez src/app/home/home.component.ts et remplacez l’intégralité du contenu du fichier par l’extrait de code suivant pour vous abonner à l’événement LOGIN_SUCCESS :

    import { Component, OnInit } from '@angular/core';
    import { MsalBroadcastService, MsalService } from '@azure/msal-angular';
    import { EventMessage, EventType, InteractionStatus } from '@azure/msal-browser';
    import { filter } from 'rxjs/operators';
    
    @Component({
      selector: 'app-home',
      templateUrl: './home.component.html',
      styleUrls: ['./home.component.css']
    })
    export class HomeComponent implements OnInit {
      constructor(private authService: MsalService, private msalBroadcastService: MsalBroadcastService) { }
    
      ngOnInit(): void {
        this.msalBroadcastService.msalSubject$
          .pipe(
            filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS),
          )
          .subscribe((result: EventMessage) => {
            console.log(result);
          });
      }
    }
    

Affichage conditionnel

Pour vous assurer que certains composants de l’interface utilisateur sont affichés uniquement pour les utilisateurs authentifiés, les composants doivent s’abonner à MsalBroadcastService pour vérifier si les utilisateurs sont connectés et si l’interaction est terminée.

  1. Ajoutez MsalBroadcastService à src/app/app.component.ts, puis abonnez-vous au inProgress$ observable pour vérifier si l’interaction a eu lieu, et si un compte est connecté avant l’affichage de l’IU. Votre code doit désormais ressembler à ceci :

    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { MsalService, MsalBroadcastService } from '@azure/msal-angular';
    import { InteractionStatus } from '@azure/msal-browser';
    import { Subject } from 'rxjs';
    import { filter, takeUntil } from 'rxjs/operators';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit, OnDestroy {
      title = 'msal-angular-tutorial';
      isIframe = false;
      loginDisplay = false;
      private readonly _destroying$ = new Subject<void>();
    
      constructor(private broadcastService: MsalBroadcastService, private authService: MsalService) { }
    
      ngOnInit() {
        this.isIframe = window !== window.parent && !window.opener;
    
        this.broadcastService.inProgress$
        .pipe(
          filter((status: InteractionStatus) => status === InteractionStatus.None),
          takeUntil(this._destroying$)
        )
        .subscribe(() => {
          this.setLoginDisplay();
        })
      }
    
      login() {
        this.authService.loginRedirect();
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    
      ngOnDestroy(): void {
        this._destroying$.next(undefined);
        this._destroying$.complete();
      }
    }
    
  2. Mettez à jour le code dans src/app/home/home.component.ts pour vérifier également si l’interaction a eu lieu avant la mise à jour de l’IU. Votre code doit désormais ressembler à ceci :

    import { Component, OnInit } from '@angular/core';
    import { MsalBroadcastService, MsalService } from '@azure/msal-angular';
    import { EventMessage, EventType, InteractionStatus } from '@azure/msal-browser';
    import { filter } from 'rxjs/operators';
    
    @Component({
      selector: 'app-home',
      templateUrl: './home.component.html',
      styleUrls: ['./home.component.css']
    })
    export class HomeComponent implements OnInit {
      loginDisplay = false;
    
      constructor(private authService: MsalService, private msalBroadcastService: MsalBroadcastService) { }
    
      ngOnInit(): void {
        this.msalBroadcastService.msalSubject$
          .pipe(
            filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS),
          )
          .subscribe((result: EventMessage) => {
            console.log(result);
          });
    
        this.msalBroadcastService.inProgress$
          .pipe(
            filter((status: InteractionStatus) => status === InteractionStatus.None)
          )
          .subscribe(() => {
            this.setLoginDisplay();
          })
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    }
    
  3. Remplacez le code dans src/app/home/home.component.html par les affichages conditionnels suivants :

    <div *ngIf="!loginDisplay">
        <p>Please sign-in to see your profile information.</p>
    </div>
    
    <div *ngIf="loginDisplay">
        <p>Login successful!</p>
        <p>Request your profile information by clicking Profile above.</p>
    </div>
    

Implémenter Angular Guard

La classe MsalGuard est l’une des classes que vous pouvez utiliser pour protéger des itinéraires et imposer une authentification avant tout accès à l’itinéraire protégé. Les étapes suivantes ajoutent MsalGuard à l’itinéraire Profile. Protéger l’itinéraire Profile signifie que même si un utilisateur ne se connecte pas à l’aide du bouton Login, s’il tente d’accéder à l’itinéraire Profile ou s’il sélectionne le bouton Profile, MsalGuard l’invite à s’authentifier via une fenêtre contextuelle ou le redirige avant d’afficher la page Profile.

MsalGuard est une classe pratique que vous pouvez utiliser pour améliorer l’expérience utilisateur. Toutefois, son utilisation est déconseillée pour des raisons de sécurité. Des attaquants peuvent éventuellement contourner les protections côté client. Par conséquent, vous devez vous assurer que le serveur ne retourne aucune donnée à laquelle l’utilisateur ne doit pas accéder.

  1. Ajoutez la classe MsalGuard en tant que fournisseur dans votre application au sein de src/app/app.module.ts, puis ajoutez les configurations pour MsalGuard. Vous pouvez fournir dans authRequest les étendues nécessaires pour obtenir des jetons plus tard, et affecter Redirect ou Popup au type d’interaction de la protection. Votre code devrait ressembler à l’extrait de code suivant :

    import { BrowserModule } from "@angular/platform-browser";
    import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
    import { NgModule } from "@angular/core";
    
    import { MatButtonModule } from "@angular/material/button";
    import { MatToolbarModule } from "@angular/material/toolbar";
    import { MatListModule } from "@angular/material/list";
    
    import { AppRoutingModule } from "./app-routing.module";
    import { AppComponent } from "./app.component";
    import { HomeComponent } from "./home/home.component";
    import { ProfileComponent } from "./profile/profile.component";
    
    import {
      MsalModule,
      MsalRedirectComponent,
      MsalGuard,
    } from "@azure/msal-angular"; // MsalGuard added to imports
    import {
      PublicClientApplication,
      InteractionType,
    } from "@azure/msal-browser"; // InteractionType added to imports
    
    const isIE =
      window.navigator.userAgent.indexOf("MSIE ") > -1 ||
      window.navigator.userAgent.indexOf("Trident/") > -1;
    
    @NgModule({
      declarations: [AppComponent, HomeComponent, ProfileComponent],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        AppRoutingModule,
        MatButtonModule,
        MatToolbarModule,
        MatListModule,
        MsalModule.forRoot(
          new PublicClientApplication({
            auth: {
              clientId: "Enter_the_Application_Id_here",
              authority:
                "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here",
              redirectUri: "Enter_the_Redirect_Uri_Here",
            },
            cache: {
              cacheLocation: "localStorage",
              storeAuthStateInCookie: isIE,
            },
          }),
          {
            interactionType: InteractionType.Redirect, // MSAL Guard Configuration
            authRequest: {
              scopes: ["user.read"],
            },
          },
          null
        ),
      ],
      providers: [
        MsalGuard, // MsalGuard added as provider here
      ],
      bootstrap: [AppComponent, MsalRedirectComponent],
    })
    export class AppModule {}
    
  2. Définissez MsalGuard pour les routes à protéger dans src/app/app-routing.module.ts :

    import { NgModule } from "@angular/core";
    import { Routes, RouterModule } from "@angular/router";
    import { BrowserUtils } from "@azure/msal-browser";
    import { HomeComponent } from "./home/home.component";
    import { ProfileComponent } from "./profile/profile.component";
    import { MsalGuard } from "@azure/msal-angular";
    
    const routes: Routes = [
      {
        path: "profile",
        component: ProfileComponent,
        canActivate: [MsalGuard],
      },
      {
        path: "",
        component: HomeComponent,
      },
    ];
    
    const isIframe = window !== window.parent && !window.opener;
    
    @NgModule({
      imports: [
        RouterModule.forRoot(routes, {
          // Don't perform initial navigation in iframes or popups
          initialNavigation:
            !BrowserUtils.isInIframe() && !BrowserUtils.isInPopup()
              ? "enabledNonBlocking"
              : "disabled", // Set to enabledBlocking to use Angular Universal
        }),
      ],
      exports: [RouterModule],
    })
    export class AppRoutingModule {}
    
  3. Ajustez les appels de connexion dans src/app/app.component.ts pour prendre en compte le authRequest défini dans les configurations de protection. Votre code devrait maintenant ressembler à l’extrait de code suivant :

    import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
    import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';
    import { InteractionStatus, RedirectRequest } from '@azure/msal-browser';
    import { Subject } from 'rxjs';
    import { filter, takeUntil } from 'rxjs/operators';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit, OnDestroy {
      title = 'msal-angular-tutorial';
      isIframe = false;
      loginDisplay = false;
      private readonly _destroying$ = new Subject<void>();
    
      constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { }
    
      ngOnInit() {
        this.isIframe = window !== window.parent && !window.opener;
    
        this.broadcastService.inProgress$
        .pipe(
          filter((status: InteractionStatus) => status === InteractionStatus.None),
          takeUntil(this._destroying$)
        )
        .subscribe(() => {
          this.setLoginDisplay();
        })
      }
    
      login() {
        if (this.msalGuardConfig.authRequest){
          this.authService.loginRedirect({...this.msalGuardConfig.authRequest} as RedirectRequest);
        } else {
          this.authService.loginRedirect();
        }
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    
      ngOnDestroy(): void {
        this._destroying$.next(undefined);
        this._destroying$.complete();
      }
    }
    

Acquérir un jeton

Classe Interceptor d’Angular

MSAL Angular fournit une classe Interceptor qui acquiert automatiquement des jetons pour les demandes sortantes qui utilisent le client http Angular pour les ressources connues protégées.

  1. Ajoutez la classe Interceptor en tant que fournisseur à votre application dans src/app/app.module.ts avec ses configurations. Votre code devrait maintenant ressembler à l’extrait de code suivant :

    import { BrowserModule } from "@angular/platform-browser";
    import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
    import { NgModule } from "@angular/core";
    import { HTTP_INTERCEPTORS, HttpClientModule } from "@angular/common/http"; // Import
    
    import { MatButtonModule } from "@angular/material/button";
    import { MatToolbarModule } from "@angular/material/toolbar";
    import { MatListModule } from "@angular/material/list";
    
    import { AppRoutingModule } from "./app-routing.module";
    import { AppComponent } from "./app.component";
    import { HomeComponent } from "./home/home.component";
    import { ProfileComponent } from "./profile/profile.component";
    
    import {
      MsalModule,
      MsalRedirectComponent,
      MsalGuard,
      MsalInterceptor,
    } from "@azure/msal-angular"; // Import MsalInterceptor
    import {
      InteractionType,
      PublicClientApplication,
    } from "@azure/msal-browser";
    
    const isIE =
      window.navigator.userAgent.indexOf("MSIE ") > -1 ||
      window.navigator.userAgent.indexOf("Trident/") > -1;
    
    @NgModule({
      declarations: [AppComponent, HomeComponent, ProfileComponent],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        AppRoutingModule,
        MatButtonModule,
        MatToolbarModule,
        MatListModule,
        HttpClientModule,
        MsalModule.forRoot(
          new PublicClientApplication({
            auth: {
              clientId: "Enter_the_Application_Id_Here",
              authority:
                "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here",
              redirectUri: "Enter_the_Redirect_Uri_Here",
            },
            cache: {
              cacheLocation: "localStorage",
              storeAuthStateInCookie: isIE,
            },
          }),
          {
            interactionType: InteractionType.Redirect,
            authRequest: {
              scopes: ["user.read"],
            },
          },
          {
            interactionType: InteractionType.Redirect, // MSAL Interceptor Configuration
            protectedResourceMap: new Map([
              ["Enter_the_Graph_Endpoint_Here/v1.0/me", ["user.read"]],
            ]),
          }
        ),
      ],
      providers: [
        {
          provide: HTTP_INTERCEPTORS,
          useClass: MsalInterceptor,
          multi: true,
        },
        MsalGuard,
      ],
      bootstrap: [AppComponent, MsalRedirectComponent],
    })
    export class AppModule {}
    

    Les ressources protégées sont fournies en tant que protectedResourceMap. Les URL que vous fournissez dans la collection protectedResourceMap sont sensibles à la casse. Pour chaque ressource, ajoutez les étendues à retourner dans le jeton d’accès.

    Par exemple :

    • ["user.read"] pour Microsoft Graph
    • ["<Application ID URL>/scope"] pour les API web personnalisées (c’est-à-dire api://<Application ID>/access_as_user)

    Modifiez les valeurs de protectedResourceMap comme indiqué ici :

    • Enter_the_Graph_Endpoint_Here est l’instance de l’API Microsoft Graph avec laquelle l’application doit communiquer. Pour le point de terminaison de l’API Microsoft Graph global, remplacez cette chaîne par https://graph.microsoft.com. Pour les points de terminaison dans les déploiements dans des clouds nationaux, consultez Déploiements dans des clouds nationaux dans la documentation Microsoft Graph.
  2. Remplacez le code dans src/app/profile/profile.component.ts pour récupérer le profil d’un utilisateur par une requête HTTP, puis remplacez GRAPH_ENDPOINT par le point de terminaison Microsoft Graph :

    import { Component, OnInit } from '@angular/core';
    import { HttpClient } from '@angular/common/http';
    
    const GRAPH_ENDPOINT = 'Enter_the_Graph_Endpoint_Here/v1.0/me';
    
    type ProfileType = {
      givenName?: string,
      surname?: string,
      userPrincipalName?: string,
      id?: string
    };
    
    @Component({
      selector: 'app-profile',
      templateUrl: './profile.component.html',
      styleUrls: ['./profile.component.css']
    })
    export class ProfileComponent implements OnInit {
      profile!: ProfileType;
    
      constructor(
        private http: HttpClient
      ) { }
    
      ngOnInit() {
        this.getProfile();
      }
    
      getProfile() {
        this.http.get(GRAPH_ENDPOINT)
          .subscribe(profile => {
            this.profile = profile;
          });
      }
    }
    
  3. Remplacez l’IU dans src/app/profile/profile.component.html pour afficher les informations de profil :

    <div>
        <p><strong>First Name: </strong> {{profile?.givenName}}</p>
        <p><strong>Last Name: </strong> {{profile?.surname}}</p>
        <p><strong>Email: </strong> {{profile?.userPrincipalName}}</p>
        <p><strong>Id: </strong> {{profile?.id}}</p>
    </div>
    

Se déconnecter

  1. Mettez à jour le code dans src/app/app.component.html pour afficher un bouton Logout de manière conditionnelle :

    <mat-toolbar color="primary">
      <a class="title" href="/">{{ title }}</a>
    
      <div class="toolbar-spacer"></div>
    
      <a mat-button [routerLink]="['profile']">Profile</a>
    
      <button mat-raised-button *ngIf="!loginDisplay" (click)="login()">Login</button>
      <button mat-raised-button *ngIf="loginDisplay" (click)="logout()">Logout</button>
    
    </mat-toolbar>
    <div class="container">
      <!--This is to avoid reload during acquireTokenSilent() because of hidden iframe -->
      <router-outlet *ngIf="!isIframe"></router-outlet>
    </div>
    

Se déconnecter à l’aide de redirections

  1. Mettez à jour le code dans src/app/app.component.ts pour déconnecter un utilisateur à l’aide de redirections :

    import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
    import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';
    import { InteractionStatus, RedirectRequest } from '@azure/msal-browser';
    import { Subject } from 'rxjs';
    import { filter, takeUntil } from 'rxjs/operators';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit, OnDestroy {
      title = 'msal-angular-tutorial';
      isIframe = false;
      loginDisplay = false;
      private readonly _destroying$ = new Subject<void>();
    
      constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { }
    
      ngOnInit() {
        this.isIframe = window !== window.parent && !window.opener;
    
        this.broadcastService.inProgress$
        .pipe(
          filter((status: InteractionStatus) => status === InteractionStatus.None),
          takeUntil(this._destroying$)
        )
        .subscribe(() => {
          this.setLoginDisplay();
        })
      }
    
      login() {
        if (this.msalGuardConfig.authRequest){
          this.authService.loginRedirect({...this.msalGuardConfig.authRequest} as RedirectRequest);
        } else {
          this.authService.loginRedirect();
        }
      }
    
      logout() { // Add log out function here
        this.authService.logoutRedirect({
          postLogoutRedirectUri: 'http://localhost:4200'
        });
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    
      ngOnDestroy(): void {
        this._destroying$.next(undefined);
        this._destroying$.complete();
      }
    }
    

Se déconnecter à l’aide de fenêtres contextuelles

  1. Mettez à jour le code dans src/app/app.component.ts pour déconnecter un utilisateur à l’aide de fenêtres contextuelles :

    import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
    import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';
    import { InteractionStatus, PopupRequest } from '@azure/msal-browser';
    import { Subject } from 'rxjs';
    import { filter, takeUntil } from 'rxjs/operators';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit, OnDestroy {
      title = 'msal-angular-tutorial';
      isIframe = false;
      loginDisplay = false;
      private readonly _destroying$ = new Subject<void>();
    
      constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { }
    
      ngOnInit() {
        this.isIframe = window !== window.parent && !window.opener;
    
        this.broadcastService.inProgress$
        .pipe(
          filter((status: InteractionStatus) => status === InteractionStatus.None),
          takeUntil(this._destroying$)
        )
        .subscribe(() => {
          this.setLoginDisplay();
        })
      }
    
      login() {
        if (this.msalGuardConfig.authRequest){
          this.authService.loginPopup({...this.msalGuardConfig.authRequest} as PopupRequest)
            .subscribe({
              next: (result) => {
                console.log(result);
                this.setLoginDisplay();
              },
              error: (error) => console.log(error)
            });
        } else {
          this.authService.loginPopup()
            .subscribe({
              next: (result) => {
                console.log(result);
                this.setLoginDisplay();
              },
              error: (error) => console.log(error)
            });
        }
      }
    
      logout() { // Add log out function here
        this.authService.logoutPopup({
          mainWindowRedirectUri: "/"
        });
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    
      ngOnDestroy(): void {
        this._destroying$.next(undefined);
        this._destroying$.complete();
      }
    }
    

Test de votre code

  1. Démarrez le serveur web pour écouter sur le port en exécutant les commandes suivantes sur une invite de ligne de commande à partir du dossier de l’application :

    npm install
    npm start
    
  2. Dans votre navigateur, entrez http://localhost:4200, et vous devriez voir une page qui ressemble à ce qui suit.

    Navigateur web affichant la boîte de dialogue de connexion

  3. Sélectionnez Accepter pour octroyer des autorisations d’application à votre profil. Cela se produit la première fois que vous commencez à vous connecter.

    Boîte de dialogue de contenu affichée dans le navigateur web

  4. Si vous consentez aux autorisations demandées, l’application web affiche une page de connexion réussie :

    Résultats d’une connexion réussie dans le navigateur web

  5. Sélectionnez Profile pour afficher les informations de profil utilisateur retournées dans la réponse de l’appel à l’API Microsoft Graph :

    Informations de profil de Microsoft Graph affichées dans le navigateur

Ajouter des étendues et des autorisations déléguées

L’API Microsoft Graph requiert l’étendue User.Read pour lire le profil d’un utilisateur. La portée User.Read est ajoutée automatiquement à chaque enregistrement d’application. D’autres API pour Microsoft Graph, ainsi que des API personnalisées pour votre serveur back-end, peuvent exiger d’autres étendues. Par exemple, l’API Microsoft Graph nécessite l’étendue Mail.Read afin de répertorier l’e-mail de l’utilisateur.

À mesure que vous ajoutez des étendues, vos utilisateurs peuvent être invités à donner un consentement supplémentaire pour les étendues ajoutées.

Notes

L’utilisateur peut être invité à donner des consentements supplémentaires à mesure que vous augmentez le nombre d’étendues.

Aide et support

Si vous avez besoin d’aide, si vous souhaitez signaler un problème ou si vous voulez en savoir plus sur vos options de support, consultez Aide et support pour les développeurs.

Étapes suivantes

  • Découvrez plus d’informations en créant une application monopage (SPA) React qui connecte les utilisateurs dans cette série de tutoriels en plusieurs parties.