Esercitazione: Eseguire l’accesso degli utenti e chiamare l'API Microsoft Graph da un'applicazione a pagina singola Angular usando il flusso del codice di autenticazione

In questa esercitazione verrà compilata un'applicazione a pagina singola Angular che esegue l'accesso degli utenti e chiama l’API Microsoft Graph usando il flusso del codice di autorizzazione con PKCE. L'applicazione a pagina singola creata usa Microsoft Authentication Library (MSAL) per Angular v2.

Contenuto dell'esercitazione:

  • Registrare l'applicazione nell'Interfaccia di amministrazione di Microsoft Entra.
  • Creare un progetto Angular con npm
  • Aggiungere il codice per supportare l'accesso e la disconnessione
  • Aggiungere il codice per chiamare l'API Microsoft Graph
  • Testare l'app

MSAL Angular v2 rappresenta un miglioramento rispetto a MSAL Angular v1, in quanto supporta il flusso del codice di autorizzazione con PKCE nel browser, al posto del flusso di concessione implicita. Si consiglia di usare il flusso del codice di autorizzazione con PKCE per applicazioni a pagina singola perché è più sicuro del flusso implicito. MSAL Angular v2 NON supporta il flusso implicito.

Prerequisiti

  • Node.js per l'esecuzione di un server Web locale.
  • Visual Studio Code o un altro editor per la modifica dei file di progetto.

Funzionamento dell'app di esempio

Diagramma che mostra il flusso del codice di autorizzazione in un'applicazione a pagina singola

L'applicazione di esempio creata in questa esercitazione consente a un'applicazione a pagina singola Angular di eseguire query sull'API Microsoft Graph o su un'API Web che accetta token rilasciati da Microsoft Identity Platform. Usa Microsoft Authentication Library (MSAL) per Angular v2, un wrapper della libreria MSAL.js v2. MSAL Angular consente alle applicazioni Angular della versione 9 e successive di autenticare gli utenti aziendali usando Microsoft Entra ID, nonché gli utenti con account Microsoft e con identità basate su social network come Facebook, Google e LinkedIn. La libreria consente inoltre di ottenere l'accesso a servizi cloud Microsoft e a Microsoft Graph.

Per questo scenario, dopo l'accesso di un utente, viene richiesto un token di accesso che viene aggiunto a richieste HTTP tramite l'intestazione dell'autorizzazione. L'acquisizione e il rinnovo dei token vengono gestiti da Microsoft Authentication Library (MSAL).

Librerie

Questa esercitazione usa le librerie seguenti:

Libreria Descrizione
MSAL Angular Microsoft Authentication Library per il wrapper Angular JavaScript
MSAL Browser Pacchetto del browser di Microsoft Authentication Library per JavaScript 2.0

È possibile trovare il codice sorgente per tutte le librerie MSAL.js nel repository microsoft-authentication-library-for-js su GitHub.

Ottenere l'esempio di codice completo

Si preferisce scaricare il progetto di esempio completato per questa esercitazione? Clonare ms-identity-javascript-angular-spa

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

Per continuare con l'esercitazione e compilare manualmente l'applicazione, passare alla sezione successiva Registrare l'applicazione e gli identificatori del record.

Registrare l'applicazione e gli identificatori del record

Suggerimento

I passaggi descritti in questo articolo possono variare leggermente in base al portale di partenza.

Per completare la registrazione indicare il nome dell'applicazione, specificare i tipi di account supportati e aggiungere un URI di reindirizzamento. Dopo la registrazione,nel riquadro Panoramica dell'applicazione vengono visualizzati gli identificatori necessari nel codice sorgente dell'applicazione.

  1. Accedere all' Interfaccia di amministrazione di Microsoft Entra almeno come Sviluppatore di applicazioni.
  2. Se si ha accesso a più tenant, usare l'icona Impostazioni nel menu in alto per passare al tenant in cui si vuole registrare l'applicazione dal menu Directory e sottoscrizioni.
  3. Passare a Identità>Applicazioni>Registrazioni app.
  4. Seleziona Nuova registrazione.
  5. Immettere un Nome per l'applicazione, ad esempio Angular-SPA-auth-code.
  6. Per Tipi di account supportati selezionare Account solo in questa directory dell'organizzazione. Per informazioni sui diversi tipi di account selezionare l'opzione Suggerimenti per la scelta.
  7. In URI di reindirizzamento (facoltativo) usare il menu a discesa per selezionare Applicazione a pagina singola e immettere http://localhost:4200 nella casella di testo.
  8. Selezionare Registra.
  9. Al termine della registrazione viene visualizzato il riquadro Panoramica dell'applicazione. Registrare l' ID directory (tenant) e l' ID applicazione (client) da usare nel codice sorgente dell'applicazione.

Creare il progetto

  1. Aprire Visual Studio Code e selezionare File>Apri cartella.... Individuare e selezionare il percorso in cui creare il progetto.

  2. Aprire un nuovo terminale selezionando Terminale>Nuovo terminale.

    1. Potrebbe essere necessario cambiare i tipi di terminale. Selezionare la freccia giù accanto all'icona + nel terminale e selezionare Prompt dei comandi.
  3. Eseguire i comandi seguenti per creare un nuovo progetto Angular denominato msal-angular-tutorial, installare le librerie dei componenti di Angular Material, MSAL Browser, MSAL Angular e generare i componenti home e profilo.

    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
    

Configurare l'applicazione e modificare l'interfaccia utente di base

  1. Aprire src/app/app.module.ts. MsalModule e MsalInterceptor devono essere aggiunti a imports insieme alla costante isIE. Devono essere aggiunti anche i moduli materiali. Sostituire l'intero contenuto del file con il frammento di codice seguente:

    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. Sostituire i valori seguenti con i valori ottenuti dall'interfaccia di amministrazione di Microsoft Entra. Per altre informazioni sulle opzioni configurabili disponibili, vedere Inizializzare le applicazioni client.

    • clientId - Identificatore dell'applicazione, detto anche client. Sostituire Enter_the_Application_Id_Here con il valore ID applicazione (client) registrato in precedenza dalla pagina di panoramica dell'applicazione registrata.
    • authority - Questo è composto da due parti:
      • L’ Istanza è l'endpoint del provider di servizi cloud. Per il cloud globale o principale di Azure, immettere https://login.microsoftonline.com. Verificare i diversi endpoint disponibili in Cloud nazionali.
      • L' ID tenant è l'identificatore del tenant dove è registrata l'applicazione. Sostituire il valore _Enter_the_Tenant_Info_Here con il valore ID directory (tenant) registrato in precedenza dalla pagina di panoramica dell'applicazione registrata.
    • redirectUri : il percorso a cui il server di autorizzazione invia l'utente dopo che l'app è stata autorizzata correttamente e ha concesso un codice di autorizzazione o un token di accesso. Sostituisci Enter_the_Redirect_Uri_Here con http://localhost:4200.
  3. Aprire src/app/app-routing.module.ts e aggiungere route ai componenti home e profilo. Sostituire l'intero contenuto del file con il frammento di codice seguente:

    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. Aprire src/app/app.component.html e sostituire il codice esistente con il frammento di codice seguente.

    <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. Aprire src/style.css per definire gli stili 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. Aprire src/app/app.component.css per aggiungere stili CSS all'applicazione:

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

Accesso tramite popup

  1. Aprire src/app/app.component.ts e sostituire il contenuto del file con il codice seguente per l’accesso di un utente tramite finestra popup:

    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;
      }
    }
    

Accesso tramite reindirizzamenti

  1. Aggiornare src/app/app.module.ts per avviare MsalRedirectComponent. Si tratta di un componente di reindirizzamento dedicato che gestisce i reindirizzamenti. Modificare MsalModuleimporta e avviaAppComponent fino a quando somiglia a quello seguente:

    ...
    import { MsalModule, MsalRedirectComponent } from '@azure/msal-angular'; // Updated import
    ...
      bootstrap: [AppComponent, MsalRedirectComponent] // MsalRedirectComponent bootstrapped here
    ...
    
  2. Aprire src/index.html e sostituire l'intero contenuto del file con il frammento di codice seguente e che aggiunge il selettore<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. Aprire src/app/app.component.ts e sostituire il codice con quello seguente per l’accesso di un utente tramite reindirizzamento full-frame.

    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. Passare a src/app/home/home.component.ts e sostituire l'intero contenuto del file con il frammento di codice seguente per sottoscrivere l'evento 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);
          });
      }
    }
    

Rendering condizionale

Per eseguire il rendering di una determinata interfaccia utente solo agli utenti autenticati, i componenti devono sottoscrivere MsalBroadcastService per verificare se gli utenti hanno eseguito l'accesso e l'interazione è stata completata.

  1. Aggiungere MsalBroadcastService a src/app/app.component.ts e sottoscrivere la funzione osservabile inProgress$ per verificare se l'interazione viene completata e se un account viene registrato prima del rendering dell'interfaccia utente. Il codice ora dovrebbe essere simile al seguente:

    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. Aggiornare il codice in src/app/home/home.component.ts per verificare anche che l'interazione venga completata prima dell'aggiornamento dell'interfaccia utente. Il codice ora dovrebbe essere simile al seguente:

    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. Sostituire il codice in src/app/home/home.component.html con le visualizzazioni condizionali seguenti:

    <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>
    

Implementare Angular Guard

La classe MsalGuard è una che è possibile usare per proteggere le route e richiedere l'autenticazione prima di accedere alla route protetta. Nei passaggi seguenti viene aggiunta la route MsalGuard alla route Profile. La protezione della route Profile significa che anche se un utente non esegue l'accesso usando il pulsante Login, se tenta di accedere alla route Profile o se seleziona il pulsante Profile, il pulsante MsalGuard richiede all'utente di eseguire l'autenticazione tramite popup o reindirizzamento prima di mostrare la paginaProfile.

MsalGuard è una classe pratica che è possibile usare per migliorare l'esperienza utente, ma non deve essere basata sulla sicurezza. Gli utenti malintenzionati possono potenzialmente aggirare i controlli lato client; assicurarsi quindi che il server non restituisca dati a cui l'utente non deve accedere.

  1. Aggiungere la classe MsalGuard come provider all'applicazione in src/app/app.module.tse aggiungere le configurazioni per MsalGuard. Gli ambiti necessari per acquisire i token in un secondo momento possono essere forniti in authRequest e il tipo di interazione per Guard può essere impostato su Redirect o Popup. Il codice sarà simile al seguente:

    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. Impostare MsalGuard nelle route da proteggere in 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. Regolare le chiamate di accesso in src/app/app.component.ts in modo da tenere conto del set authRequest nelle configurazioni dei controlli. Il codice dovrebbe ora essere simile al seguente:

    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();
      }
    }
    

Acquisire un token

Intercettore Angular

Microsoft Authentication Library per Angular fornisce una classe Interceptor che acquisisce automaticamente i token per le richieste in uscita che usano il client http Angular per le risorse protette note.

  1. Aggiungere la classe Interceptor come provider all'applicazione in src/app/app.module.ts, con le relative configurazioni. Il codice dovrebbe ora essere simile al seguente:

    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 {}
    

    Le risorse protette vengono fornite come protectedResourceMap. Gli URL specificati nella raccolta protectedResourceMap fanno distinzione tra maiuscole e minuscole. Per ogni risorsa, aggiungere gli ambiti da restituire nel token di accesso.

    Ad esempio:

    • ["user.read"] per Microsoft Graph
    • ["<Application ID URL>/scope"] per le API Web personalizzate, ovvero api://<Application ID>/access_as_user

    Modificare i valori in protectedResourceMap come descritto di seguito:

    • Enter_the_Graph_Endpoint_Here è l'istanza dell'API Microsoft Graph con cui l'applicazione dovrà comunicare. Per l'endpoint dell'API Microsoft Graph globale sostituire questa stringa con https://graph.microsoft.com. Per gli endpoint delle distribuzioni di cloud nazionali, vedere Distribuzioni di cloud nazionali nella documentazione di Microsoft Graph.
  2. Sostituire il codice in src/app/profile/profile.component.ts per recuperare il profilo utente con richiesta HTTP e sostituire GRAPH_ENDPOINT con l'endpoint di 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. Sostituire l'interfaccia utente in src/app/profile/profile.component.html per visualizzare le informazioni sul profilo:

    <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>
    

Disconnettersi

  1. Aggiornare il codice in src/app/app.component.html per visualizzare in modo condizionale un pulsanteLogout:

    <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>
    

Disconnessione tramite reindirizzamenti

  1. Aggiornare il codice in src/app/app.component.ts per disconnettere un utente tramite reindirizzamenti:

    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();
      }
    }
    

Disconnessione tramite popup

  1. Aggiornare il codice in src/app/app.component.ts per disconnettere un utente tramite popup:

    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();
      }
    }
    

Eseguire test del codice

  1. Avviare il server Web in modo che rimanga in ascolto sulla porta eseguendo questi comandi da un prompt della riga di comando nella cartella dell'applicazione:

    npm install
    npm start
    
  2. Nel browser immettere http://localhost:4200 e verrà visualizzata una pagina simile alla seguente.

    Web browser che visualizza la finestra di dialogo di accesso

  3. Selezionare Accetta per concedere le autorizzazioni dell'app al profilo. Questa operazione si verifica la prima volta che si esegue l’accesso.

    Finestra di dialogo di contenuto visualizzata nel Web browser

  4. Dopo il consenso alle autorizzazioni richieste, l'applicazione Web visualizza una pagina di accesso corretta.

    Risultati di un accesso riuscito nel Web browser

  5. Selezionare Profilo per visualizzare le informazioni del profilo utente restituite in risposta alla chiamata all'API Microsoft Graph:

    Informazioni del profilo di Microsoft Graph visualizzate nel browser

Aggiungere ambiti e autorizzazioni delegate

L'API Microsoft Graph richiede l'ambito User.Read per leggere il profilo di un utente. L'ambito User.Read viene aggiunto automaticamente a ogni registrazione dell'app. Altre API per Microsoft Graph e le API personalizzate per il server back-end possono richiedere anche altri ambiti. Ad esempio, l'API Microsoft Graph richiede l'ambito Mail.Read per visualizzare la posta elettronica dell'utente.

Aggiungendo altri ambiti, agli utenti può essere richiesto di fornire un consenso aggiuntivo per gli ambiti aggiunti.

Nota

Con l'aumentare del numero di ambiti è possibile che all'utente venga chiesto di esprimere anche altri tipi di consenso.

Assistenza e supporto

Se è necessaria assistenza, si vuole segnalare un problema o si vogliono ottenere informazioni sulle opzioni di supporto, vedere Assistenza e supporto per gli sviluppatori.

Passaggi successivi

  • Per approfondimento, compilare un'applicazione a pagina singola che effettua l’accesso degli utenti nella seguente serie di esercitazioni in più parti.