События в MSAL Angular

Перед началом работы убедитесь, что вы узнаете, как инициализировать объект приложения.

@azure/msal-angular использует систему событий, предоставленную @azure/msal-browser, которая выдает события, связанные с проверкой подлинности и MSAL, и может использоваться для обновления пользовательского интерфейса, отображения сообщений об ошибках и т. д.

Обработка событий в вашем приложении

События в @azure/msal-angular управляются MsalBroadcastService и доступны при подписке на наблюдаемый объект msalSubject$ в MsalBroadcastService.

Вот пример того, как можно обрабатывать отправляемые события в вашем приложении:

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

Обратите внимание, что для предотвращения ошибок компиляции может потребоваться приведение result.payload в качестве определенного типа. Тип полезной нагрузки будет зависеть от события, и его можно найти в нашей документации здесь.

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

Полный пример использования событий см. здесь.

Таблица событий

Дополнительные сведения об объекте EventMessage, включая полную таблицу событий, в настоящее время отправляемых @azure/msal-browser (включая описания и связанные данные полезной нагрузки), см. в документации здесь.

Обработка ошибок с помощью событий

Поскольку EventError в EventMessage определяется как AuthError | Error | null, перед обращением к её определённым свойствам следует проверить, что ошибка имеет правильный тип.

Ниже приведен пример того, как можно привести ошибку к типу AuthError, чтобы избежать ошибок 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();
  }
}

Пример обработки ошибок также можно найти в нашем примере MSAL Angular B2C.

Синхронизация состояния входа в систему между вкладками и окнами

Если вы хотите обновлять интерфейс, когда пользователь выполняет вход в приложение или выходит из учётной записи в другой вкладке или окне, вы можете подписаться на события ACCOUNT_ADDED и ACCOUNT_REMOVED. В качестве полезной нагрузки будет передан объект AccountInfo, который был добавлен или удалён.

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

Полный пример также можно найти в наших примерах.

Наблюдаемое значение inProgress$

Наблюдаемый объект inProgress$ также обрабатывается с помощью MsalBroadcastService, и на него следует подписаться, когда приложению необходимо знать состояние взаимодействий, в частности, чтобы проверить, завершены ли взаимодействия. Рекомендуется InteractionStatus.None проверить состояние взаимодействия перед функциями, связанными с учетными записями пользователей.

Обратите внимание, что последний / самый недавний InteractionStatus будет также доступен при подписке на наблюдаемый объект inProgress$.

См. приведенный ниже пример для его использования. Полный пример также можно найти в наших примерах. Полный список состояний взаимодействия можно найти здесь.

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

Необязательные MsalBroadcastService конфигурации

MsalBroadcastService при необходимости можно настроить так, чтобы при подписке на него он повторно воспроизводил прошлые события. По умолчанию доступны события, генерируемые после подписки на MsalBroadcastService. В некоторых случаях могут потребоваться события, предшествующие подписке. Если предоставить конфигурацию для MsalBroadcastService и установить параметру eventsToReplay числовое значение, то при подписке будет доступно соответствующее количество прошлых событий.

Дополнительные сведения о повторном отображении событий см. в документации RxJS по replaySubjects здесь.

MsalBroadcastService можно настроить в файле app.module.ts следующим образом:

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