Eventos no MSAL Angular

Antes de começares aqui, certifica-te de que compreendes como inicializar o objeto da aplicação.

@azure/msal-angular utiliza o sistema de eventos exposto por @azure/msal-browser, que emite eventos relacionados com autenticação e MSAL, e pode ser usado para atualizar a interface, mostrar mensagens de erro, e assim sucessivamente.

Consumir eventos na sua aplicação

Os eventos em @azure/msal-angular são geridos pelo MsalBroadcastService, e estão disponíveis subscrevendo o msalSubject$ observável no MsalBroadcastService.

Aqui está um exemplo de como pode consumir os eventos emitidos na sua aplicação:

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

Note que pode ser necessário lançar o result.payload como um tipo específico para evitar erros de compilação. O tipo de carga útil dependerá do evento e pode ser encontrado na nossa documentação aqui.

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 o exemplo completo de utilização de eventos, por favor consulte o nosso exemplo aqui.

Tabela de eventos

Para mais informações sobre o EventMessage objeto, incluindo a tabela completa de eventos atualmente emitidos por @azure/msal-browser (incluindo descrições e cargas úteis relacionadas), consulte a documentação aqui.

Gestão de erros com eventos

Como o EventError in EventMessage é definido como AuthError | Error | null, um erro deve ser validado como o tipo correto antes de aceder a propriedades específicas sobre ele.

Veja o exemplo abaixo de como um erro pode ser convertido para AuthError, de modo a evitar erros do 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();
  }
}

Um exemplo de tratamento de erros pode também ser encontrado na nossa Amostra MSAL Angular B2C.

Sincronização do estado de login entre separadores e janelas

Se pretender atualizar a interface do utilizador quando um utilizador inicia ou termina sessão na sua aplicação noutro separador ou janela, pode subscrever os eventos ACCOUNT_ADDED e ACCOUNT_REMOVED. A carga útil será o AccountInfo objeto que foi adicionado ou removido.

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

Um exemplo completo também pode ser encontrado nas nossas amostras.

O observável inProgress$

O observável inProgress$ também é tratado pelo MsalBroadcastService e deve ser subscrito quando a aplicação precisar de saber o estado das interações, nomeadamente para verificar se estas estão concluídas. Recomendamos confirmar que o estado das interações é InteractionStatus.None antes de executar funções que envolvam contas de utilizador.

Tenha em atenção que o último / mais recente InteractionStatus também estará disponível ao subscrever o observável inProgress$.

Veja o exemplo abaixo para a sua utilização. Um exemplo completo também pode ser encontrado nas nossas amostras. Uma lista completa dos estados das interações pode ser encontrada aqui.

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

Configurações Opcionais MsalBroadcastService

O MsalBroadcastService pode ser configurado opcionalmente para reproduzir eventos anteriores ao subscrever. Por predefinição, os eventos emitidos depois de subscrever MsalBroadcastService estão disponíveis. Podem existir situações em que sejam necessários eventos antes da subscrição. Ao fornecer uma configuração para o MsalBroadcastService e definir o eventsToReplay parâmetro para um número, esse número de eventos passados estará disponível mediante subscrição.

Para mais informações sobre a repetição de eventos, consulte a documentação do RxJS no ReplaySubjects aqui.

O MsalBroadcastService pode ser configurado no ficheiro app.module.ts da seguinte forma:

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