Eventos en MSAL Angular

Antes de empezar aquí, asegúrese de comprender cómo inicializar el objeto de aplicación.

@azure/msal-angular usa el sistema de eventos expuesto por @azure/msal-browser, que emite eventos relacionados con la autenticación y MSAL, y se puede usar para actualizar la interfaz de usuario, mostrando mensajes de error, etc.

Consumo de eventos en tu aplicación

Los eventos de @azure/msal-angular son gestionados por el MsalBroadcastService y están disponibles al suscribirse al observable msalSubject$ en el MsalBroadcastService.

Este es un ejemplo de cómo puede consumir los eventos emitidos en la aplicación:

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

Tenga en cuenta que es posible que tenga que convertir result.payload a un tipo específico para evitar errores de compilación. El tipo de carga dependerá del evento y se puede encontrar en nuestra documentación aquí.

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

Para obtener el ejemplo completo de uso de eventos, consulte nuestro ejemplo aquí.

Tabla de eventos

Para obtener más información sobre el EventMessage objeto, incluida la tabla completa de eventos emitidos actualmente por @azure/msal-browser (incluidas las descripciones y las cargas relacionadas), consulte la documentación aquí.

Manejo de errores con eventos

Como EventError en EventMessage se define como AuthError | Error | null, se debe validar que un error sea del tipo correcto antes de acceder a propiedades específicas en este.

Consulta el ejemplo siguiente sobre cómo se puede convertir un error a AuthError para evitar errores de 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();
  }
}

También se puede encontrar un ejemplo de control de errores en nuestro ejemplo de MSAL Angular B2C.

Sincronización del estado de inicio de sesión entre pestañas y ventanas

Si quiere actualizar la interfaz de usuario cuando un usuario inicia sesión o sale de la aplicación en otra pestaña o ventana, puede suscribirse a los ACCOUNT_ADDED eventos y ACCOUNT_REMOVED . La carga será el AccountInfo objeto que se agregó o quitó.

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

También se puede encontrar un ejemplo completo en nuestros ejemplos.

El observable inProgress$

El observable inProgress$ también es gestionado por MsalBroadcastService, y se debe suscribir a él cuando la aplicación necesite conocer el estado de las interacciones, en particular para comprobar que las interacciones se han completado. Se recomienda comprobar que el estado de las interacciones es InteractionStatus.None anterior a las funciones que implican cuentas de usuario.

Ten en cuenta que el último InteractionStatus o el más reciente también estará disponible al suscribirte al observable inProgress$.

Consulte el ejemplo siguiente para su uso. También se puede encontrar un ejemplo completo en nuestros ejemplos. Puede encontrar una lista completa de los estados de interacción aquí.

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

Configuraciones opcionales MsalBroadcastService

El MsalBroadcastService se puede configurar opcionalmente para reproducir eventos pasados al suscribirse a él. De forma predeterminada, están disponibles los eventos que se emiten después de suscribirse a MsalBroadcastService. Puede haber instancias en las que se necesiten eventos antes de la suscripción. Al proporcionar una configuración para MsalBroadcastService y establecer el parámetro eventsToReplay en un número, ese número de eventos anteriores estará disponible al suscribirse.

Para obtener más información sobre la reproducción de eventos, consulte los documentos de RxJS en ReplaySubjects aquí.

MsalBroadcastService se puede configurar en el archivo app.module.ts de la siguiente manera:

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