Eventi in MSAL Angular

Prima di iniziare, assicurarsi di comprendere come inizializzare l'oggetto applicazione.

@azure/msal-angular usa il sistema eventi esposto da @azure/msal-browser, che genera eventi correlati all'autenticazione e MSAL e può essere usato per l'aggiornamento dell'interfaccia utente, la visualizzazione dei messaggi di errore e così via.

Gestione degli eventi nell'app

Gli eventi in @azure/msal-angular vengono gestiti dal MsalBroadcastService e sono disponibili sottoscrivendosi all'osservabile msalSubject$ nel MsalBroadcastService.

Di seguito è riportato un esempio di come usare gli eventi generati nell'applicazione:

import { MsalBroadcastService } from '@azure/msal-angular';
import { EventMessage, EventType } from '@azure/msal-browser';

export class AppComponent implements OnInit, OnDestroy {
  private readonly _destroying$ = new Subject<void>();

  constructor(
    //...
    private msalBroadcastService: MsalBroadcastService
  ) {}

  ngOnInit(): void {
    this.msalBroadcastService.msalSubject$
      .pipe(
        // Optional filtering of events.
        filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS), 
        takeUntil(this._destroying$)
      )
      .subscribe((result: EventMessage) => {
        // Do something with the result
      });
  }

  ngOnDestroy(): void {
    this._destroying$.next(null);
    this._destroying$.complete();
  }
}

Si noti che potrebbe essere necessario fare il cast di result.payload a un tipo specifico per evitare errori di compilazione. Il tipo di payload dipenderà dall'evento ed è disponibile nella documentazione qui.

ngOnInit(): void {
  this.msalBroadcastService.msalSubject$
    .pipe(
      filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS),
    )
    .subscribe((result: EventMessage) => {
      // Casting payload as AuthenticationResult to access account
      const payload = result.payload as AuthenticationResult;
      this.authService.instance.setActiveAccount(payload.account);
    });
}

Per l'esempio completo dell'uso degli eventi, vedere l'esempio qui.

Tabella degli eventi

Per altre informazioni sull'oggetto EventMessage , inclusa la tabella completa degli eventi attualmente generati da @azure/msal-browser (incluse le descrizioni e i payload correlati), vedere la documentazione qui.

Gestione degli errori con gli eventi

Poiché l'elemento EventError in EventMessage è definito come AuthError | Error | null, è necessario verificare che un errore sia del tipo corretto prima di accedere alle sue proprietà specifiche.

Vedi l'esempio seguente di come convertire un errore in AuthError per evitare errori di TypeScript:

import { MsalBroadcastService } from '@azure/msal-angular';
import { EventMessage, EventType } from '@azure/msal-browser';

export class AppComponent implements OnInit, OnDestroy {
  private readonly _destroying$ = new Subject<void>();

  constructor(
    //...
    private msalBroadcastService: MsalBroadcastService
  ) {}

  ngOnInit(): void {
    this.msalBroadcastService.msalSubject$
      .pipe(
        // Optional filtering of events
        filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_FAILURE), 
        takeUntil(this._destroying$)
      )
      .subscribe((result: EventMessage) => {
        if (result.error instanceof AuthError) {
          // Do something with the error
        }
      });
  }

  ngOnDestroy(): void {
    this._destroying$.next(null);
    this._destroying$.complete();
  }
}

Un esempio di gestione degli errori è disponibile anche nell'esempio MSAL Angular B2C.

Sincronizzazione dello stato di accesso tra schede e finestre

Se vuoi aggiornare l'interfaccia utente quando un utente effettua l'accesso o si disconnette dalla tua app in un'altra scheda o finestra, puoi iscriverti agli eventi ACCOUNT_ADDED e ACCOUNT_REMOVED. Il payload sarà l'oggetto AccountInfo aggiunto o rimosso.

import { MsalService, MsalBroadcastService } from '@azure/msal-angular';
import { EventMessage, EventType } from '@azure/msal-browser';

export class AppComponent implements OnInit, OnDestroy {
  private readonly _destroying$ = new Subject<void>();

  constructor(
    //...
    private authService: MsalService,
    private msalBroadcastService: MsalBroadcastService
  ) {}

  ngOnInit(): void {
    this.authService.instance.enableAccountStorageEvents(); // Register the storage listener that will be emitting the events
    this.msalBroadcastService.msalSubject$
      .pipe(
        // Optional filtering of events
        filter((msg: EventMessage) => msg.eventType === EventType.ACCOUNT_ADDED || msg.eventType === EventType.ACCOUNT_REMOVED), 
        takeUntil(this._destroying$)
      )
      .subscribe((result: EventMessage) => {
        if (this.authService.msalInstance.getAllAccounts().length === 0) {
          // Account logged out in a different tab, redirect to homepage
          window.location.pathname = "/";
        } else {
          // Update UI to show user is signed in. result.payload contains the account that was logged in
        }
      });
  }

  ngOnDestroy(): void {
    this._destroying$.next(null);
    this._destroying$.complete();
  }
}

Un esempio completo è disponibile anche negli esempi.

L'osservabile inProgress$

L'osservabile inProgress$ viene gestito anche da MsalBroadcastServicee deve essere sottoscritto quando l'applicazione deve conoscere lo stato delle interazioni, in particolare per verificare che le interazioni siano completate. È consigliabile verificare che lo stato delle interazioni sia InteractionStatus.None prima delle funzioni che coinvolgono gli account utente.

Si noti che l'ultimo/più recente InteractionStatus sarà disponibile anche quando si sottoscrive l'osservabile inProgress$ .

Vedere l'esempio seguente per il relativo uso. Un esempio completo è disponibile anche negli esempi. Un elenco completo degli stati di interazione è disponibile qui.

import { Component, OnInit, Inject, OnDestroy } from '@angular/core';
import { 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 {
  private readonly _destroying$ = new Subject<void>();

  constructor(
    private msalBroadcastService: MsalBroadcastService
  ) {}

  ngOnInit(): void {
    this.msalBroadcastService.inProgress$
      .pipe(
        // Filtering for all interactions to be completed
        filter((status: InteractionStatus) => status === InteractionStatus.None),
        takeUntil(this._destroying$)
      )
      .subscribe(() => {
        // Do something related to user accounts or UI here
      })
  }

  ngOnDestroy(): void {
    this._destroying$.next(null);
    this._destroying$.complete();
  }
}

Configurazioni facoltative MsalBroadcastService

Il MsalBroadcastService può essere configurato facoltativamente per riprodurre eventi passati al momento della sottoscrizione. Per impostazione predefinita, sono disponibili gli eventi generati dopo la sottoscrizione a MsalBroadcastService. Potrebbero essere presenti istanze in cui sono necessari eventi prima della sottoscrizione. Fornendo una configurazione per MsalBroadcastService e impostando il eventsToReplay parametro su un numero, tale numero di eventi precedenti sarà disponibile nella sottoscrizione.

Per altre informazioni sulla riproduzione degli eventi, vedere la documentazione di RxJS su ReplaySubjects qui.

MsalBroadcastService può essere configurato nel file app.module.ts come segue:

// app.module.ts
import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { MsalModule, MsalService, MsalGuard, MsalInterceptor, MsalBroadcastService, MsalRedirectComponent, MSAL_BROADCAST_CONFIG } from "@azure/msal-angular"; // Import MsalBroadcastService and MSAL_BROADCAST_CONFIG here
import { PublicClientApplication, InteractionType, BrowserCacheLocation } from "@azure/msal-browser";

@NgModule({
    imports: [
        MsalModule.forRoot( new PublicClientApplication({ // MSAL Configuration
            auth: {
                clientId: "clientid",
                authority: "https://login.microsoftonline.com/common/",
                redirectUri: "http://localhost:4200/",
                postLogoutRedirectUri: "http://localhost:4200/",
                navigateToLoginRequestUrl: true
            },
            cache: {
                cacheLocation : BrowserCacheLocation.LocalStorage,
            },
            system: {
                loggerOptions: {
                    loggerCallback: () => {},
                    piiLoggingEnabled: false
                }
            }
        }), {
            interactionType: InteractionType.Popup, // MSAL Guard Configuration
            authRequest: {
              scopes: ['user.read']
            },
            loginFailedRoute: "/login-failed" 
        }, {
            interactionType: InteractionType.Redirect, // MSAL Interceptor Configuration
            protectedResourceMap
        })
    ],
    providers: [
        {
            provide: HTTP_INTERCEPTORS,
            useClass: MsalInterceptor,
            multi: true
        },
        {
          provide: MSAL_BROADCAST_CONFIG, // Add configuration to providers here
          useValue: {
            eventsToReplay: 2 // Set how many events you want to replay when subscribing
          }
        },
        MsalGuard,
        MsalBroadcastService // Ensure the MsalBroadcastService is provided
    ],
    bootstrap: [AppComponent, MsalRedirectComponent]
})
export class AppModule {}