Конфигурация MSAL Angular

MSAL для Angular можно настроить несколькими способами. В этой статье описываются доступные параметры конфигурации MSAL Angular, включая статические и динамические подходы, а также примеры кода для интеграции проверки подлинности в приложение. Используйте это руководство, чтобы выбрать метод конфигурации, который лучше всего соответствует требованиям приложения и обеспечивает простой вход для пользователей.

Параметры конфигурации

@azure/msal-angular принимает три объекта конфигурации:

  1. Конфигурация. Это тот же объект конфигурации, который используется для основной @azure/msal-browser библиотеки. Здесь можно найти все параметры конфигурации.
  2. MsalGuardConfiguration: набор параметров, предназначенных специально для Angular guard.
  3. MsalInterceptorConfiguration: набор параметров, предназначенных для перехватчика Angular.

Конфигурации, относящиеся к Angular

  • Для MsalGuardConfiguration и MsalInterceptorConfiguration необходимо указать interactionType; можно задать значение Popup или Redirect.
  • Объект protectedResourceMap на MsalInterceptorConfiguration используется для защиты маршрутов.
  • Необязательный объект authRequest можно указать в MsalGuardConfiguration и MsalInterceptorConfiguration, чтобы задать дополнительные параметры.
  • На MsalGuardConfiguration можно задать необязательную строку loginFailedRoute. Msal Guard перенаправит на этот маршрут, если требуется вход в систему и попытка входа завершается ошибкой.

Дополнительные сведения о конфигурациях, использовании и различиях с MSAL Angular версии 1 см. в наших документах msalInterceptor и MsalGuard .

Конфигурация перенаправлений

Мы рекомендуем импортировать MsalRedirectComponent и инициализировать приложение с помощью AppComponent, если вы планируете использовать перенаправления. Дополнительные сведения см. в документации по перенаправлению .

Примечание: По состоянию на MSAL версии 3.x теперь требуется инициализация объекта приложения. Дополнительные сведения см. в руководстве по обновлению версии 2-3 . Дополнительные сведения об обновлении до MSAL Angular версии 5 см. в руководстве по обновлению версии 4-v5.

MsalModule.forRoot

Класс MsalModule содержит статический метод, который можно вызвать в 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 } from "@azure/msal-angular";
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/",
        },
        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,
    },
    MsalGuard,
  ],
  bootstrap: [AppComponent, MsalRedirectComponent],
})
export class AppModule {}

Поставщики фабрики

Вы также можете передать параметры конфигурации с помощью фабричных провайдеров.

import { MsalModule, MsalService, MsalInterceptor, MsalInterceptorConfiguration, MsalGuard, MsalGuardConfiguration, MsalBroadcastService, MsalRedirectComponent } from "@azure/msal-angular";
import { IPublicClientApplication, PublicClientApplication, InteractionType, BrowserCacheLocation } from "@azure/msal-browser";

export function MSALInstanceFactory(): IPublicClientApplication {
  return new PublicClientApplication({
    auth: {
      clientId: "00001111-aaaa-2222-bbbb-3333cccc4444",
      redirectUri: "http://localhost:4200",
      postLogoutRedirectUri: "http://localhost:4200",
    },
    cache: {
      cacheLocation: BrowserCacheLocation.LocalStorage,
    },
  });
}

export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
  const protectedResourceMap = new Map<string, Array<string>>();
  protectedResourceMap.set("https://graph.microsoft.com/v1.0/me", ["user.read"]);

  return {
    interactionType: InteractionType.Redirect,
    protectedResourceMap,
  };
}

export function MSALGuardConfigFactory(): MsalGuardConfiguration {
  return {
    interactionType: InteractionType.Redirect,
    authRequest: {
      scopes: ["user.read"],
    },
    loginFailedRoute: "./login-failed",
  };
}

@NgModule({
  imports: [MsalModule],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: MsalInterceptor,
      multi: true,
    },
    {
      provide: MSAL_INSTANCE,
      useFactory: MSALInstanceFactory,
    },
    {
      provide: MSAL_GUARD_CONFIG,
      useFactory: MSALGuardConfigFactory,
    },
    {
      provide: MSAL_INTERCEPTOR_CONFIG,
      useFactory: MSALInterceptorConfigFactory,
    },
    MsalGuard,
    MsalBroadcastService,
    MsalService,
  ],
  bootstrap: [AppComponent, MsalRedirectComponent],
})
export class AppModule {}

platformBrowserDynamic

Если необходимо динамически настроить MSAL Angular (например, на основе значений, возвращаемых из API), можно использовать platformBrowserDynamic. platformBrowserDynamic — это фабрика платформы, используемая для инициализации приложения и способная принимать параметры конфигурации. platformBrowserDynamic уже должен присутствовать при настройке приложения Angular.

Ниже приведен пример динамической настройки @azure/msal-angular с platformBrowserDynamic файлом JSON.

app.module.ts

import { MsalModule, MsalInterceptor, MsalService } from "@azure/msal-angular";

@NgModule({
  imports: [MsalModule],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: MsalInterceptor,
      multi: true,
    },
    MsalService,
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

main.ts

import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";

import { AppModule } from "./app/app.module";
import { environment } from "./environments/environment";
import { MSAL_INSTANCE, MSAL_GUARD_CONFIG, MSAL_INTERCEPTOR_CONFIG } from "@azure/msal-angular";
import { PublicClientApplication, Configuration } from "@azure/msal-browser";

if (environment.production) {
  enableProdMode();
}

function loggerCallback(logLevel: LogLevel, message: string) {
  console.log("MSAL Angular: ", message);
}

fetch("/assets/configuration.json")
  .then((response) => response.json())
  .then((json) => {
    platformBrowserDynamic([
      {
        provide: MSAL_INSTANCE,
        useValue: new PublicClientApplication({
          auth: json.msal.auth,
          cache: json.msal.cache,
          system: {
            loggerOptions: {
              loggerCallback,
              logLevel: LogLevel.Info,
              piiLoggingEnabled: false,
            },
          },
        }),
      },
      {
        provide: MSAL_GUARD_CONFIG,
        useValue: {
          interactionType: json.guard.interactionType,
          authRequest: json.guard.authRequest,
          loginFailedRoute: json.guard.loginFailedRoute,
        } as MsalGuardConfiguration,
      },
      {
        provide: MSAL_INTERCEPTOR_CONFIG,
        useValue: {
          interactionType: json.interceptor.interactionType,
          protectedResourceMap: new Map(json.interceptor.protectedResourceMap),
        } as MsalInterceptorConfiguration,
      },
    ])
      .bootstrapModule(AppModule)
      .catch((err) => console.error(err));
  });

src/assets/configuration.json

{
  "msal": {
    "auth": {
      "clientId": "clientid",
      "authority": "https://login.microsoftonline.com/common/",
      "redirectUri": "http://localhost:4200/",
      "postLogoutRedirectUri": "http://localhost:4200/",
      "navigateToLoginRequestUrl": true
    },
    "cache": {
      "cacheLocation": "localStorage",
      "storeAuthStateInCookie": true
    }
  },
  "guard": {
    "interactionType": "redirect",
    "authRequest": {
      "scopes": ["user.read"]
    },
    "loginFailedRoute": "/login-failed"
  },
  "interceptor": {
    "interactionType": "redirect",
    "protectedResourceMap": [["https://graph.microsoft.com/v1.0/me", ["user.read"]]]
  }
}

Динамические конфигурации с использованием провайдеров-фабрик и APP_INITIALIZER

Для динамической настройки MSAL Angular можно использовать фабричные провайдеры с APP_INITIALIZER.

src/app/config.service.ts

import { Injectable } from "@angular/core";
import { HttpClient, HttpBackend } from "@angular/common/http";
import { map } from "rxjs/operators";

@Injectable({
  providedIn: "root",
})
export class ConfigService {
  private settings: any;
  private http: HttpClient;

  constructor(private readonly httpHandler: HttpBackend) {
    this.http = new HttpClient(httpHandler);
  }

  init(endpoint: string): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => {
      this.http
        .get(endpoint)
        .pipe(map((result) => result))
        .subscribe(
          (value) => {
            this.settings = value;
            resolve(true);
          },
          (error) => {
            reject(error);
          }
        );
    });
  }

  getSettings(key?: string | Array<string>): any {
    if (!key || (Array.isArray(key) && !key[0])) {
      return this.settings;
    }

    if (!Array.isArray(key)) {
      key = key.split(".");
    }

    let result = key.reduce((account: any, current: string) => account && account[current], this.settings);

    return result;
  }
}

src/app/msal-config-dynamic.module.ts

import { InjectionToken, NgModule, APP_INITIALIZER } from "@angular/core";
import { IPublicClientApplication, PublicClientApplication, LogLevel } from "@azure/msal-browser";
import { MsalGuard, MsalInterceptor, MsalBroadcastService, MsalInterceptorConfiguration, MsalModule, MsalService, MSAL_GUARD_CONFIG, MSAL_INSTANCE, MSAL_INTERCEPTOR_CONFIG, MsalGuardConfiguration } from "@azure/msal-angular";
import { HTTP_INTERCEPTORS } from "@angular/common/http";
import { ConfigService } from "./config.service";

const AUTH_CONFIG_URL_TOKEN = new InjectionToken<string>("AUTH_CONFIG_URL");

export function initializerFactory(env: ConfigService, configUrl: string): any {
  const promise = env.init(configUrl).then((value) => {
    console.log("finished getting configurations dynamically.");
  });
  return () => promise;
}

export function loggerCallback(logLevel: LogLevel, message: string) {
  console.log(message);
}

export function MSALInstanceFactory(config: ConfigService): IPublicClientApplication {
  return new PublicClientApplication({
    auth: config.getSettings("msal").auth,
    cache: config.getSettings("msal").cache,
    system: {
      loggerOptions: {
        loggerCallback,
        logLevel: LogLevel.Info,
        piiLoggingEnabled: false,
      },
    },
  });
}

export function MSALInterceptorConfigFactory(config: ConfigService): MsalInterceptorConfiguration {
  const protectedResourceMap = new Map<string, Array<string>>(config.getSettings("interceptor").protectedResourceMap);

  return {
    interactionType: config.getSettings("interceptor").interactionType,
    protectedResourceMap,
  };
}

export function MSALGuardConfigFactory(config: ConfigService): MsalGuardConfiguration {
  return {
    interactionType: config.getSettings("guard").interactionType,
    authRequest: config.getSettings("guard").authRequest,
    loginFailedRoute: config.getSettings("guard").loginFailedRoute,
  };
}

@NgModule({
  providers: [],
  imports: [MsalModule],
})
export class MsalConfigDynamicModule {
  static forRoot(configFile: string) {
    return {
      ngModule: MsalConfigDynamicModule,
      providers: [
        ConfigService,
        { provide: AUTH_CONFIG_URL_TOKEN, useValue: configFile },
        { provide: APP_INITIALIZER, useFactory: initializerFactory, deps: [ConfigService, AUTH_CONFIG_URL_TOKEN], multi: true },
        {
          provide: MSAL_INSTANCE,
          useFactory: MSALInstanceFactory,
          deps: [ConfigService],
        },
        {
          provide: MSAL_GUARD_CONFIG,
          useFactory: MSALGuardConfigFactory,
          deps: [ConfigService],
        },
        {
          provide: MSAL_INTERCEPTOR_CONFIG,
          useFactory: MSALInterceptorConfigFactory,
          deps: [ConfigService],
        },
        MsalService,
        MsalGuard,
        MsalBroadcastService,
        {
          provide: HTTP_INTERCEPTORS,
          useClass: MsalInterceptor,
          multi: true,
        },
      ],
    };
  }
}

src/app/app.module.ts

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 { HttpClientModule } from "@angular/common/http";
import { MsalRedirectComponent } from "@azure/msal-angular";
import { DetailComponent } from "./detail/detail.component";
import { MsalConfigDynamicModule } from "./msal-config-dynamic.module";

@NgModule({
  declarations: [AppComponent, HomeComponent, ProfileComponent, DetailComponent],
  imports: [BrowserModule, BrowserAnimationsModule, AppRoutingModule, MatButtonModule, MatToolbarModule, MatListModule, HttpClientModule, MsalConfigDynamicModule.forRoot("assets/configuration.json")],
  providers: [],
  bootstrap: [AppComponent, MsalRedirectComponent],
})
export class AppModule {}

src/assets/configuration.json

{
  "msal": {
    "auth": {
      "clientId": "clientid",
      "authority": "https://login.microsoftonline.com/common/",
      "redirectUri": "http://localhost:4200/",
      "postLogoutRedirectUri": "http://localhost:4200/",
      "navigateToLoginRequestUrl": true
    },
    "cache": {
      "cacheLocation": "localStorage",
      "storeAuthStateInCookie": true
    }
  },
  "guard": {
    "interactionType": "redirect",
    "authRequest": {
      "scopes": ["user.read"]
    },
    "loginFailedRoute": "/login-failed"
  },
  "interceptor": {
    "interactionType": "redirect",
    "protectedResourceMap": [["https://graph.microsoft.com/v1.0/me", ["user.read"]]]
  }
}

MsalGuard — динамический запрос проверки подлинности

MsalGuard также позволяет динамически изменять authRequest во время выполнения. Это позволяет выбрать другого поставщика аутентификации для маршрута или динамически добавлять области действия на основе RouterStateSnapshot.

export function MSALGuardConfigFactory(): MsalGuardConfiguration {
  return {
    interactionType: InteractionType.Redirect,
    authRequest: (authService, state) => {
      return {
        scopes: state.root.url.some((x) => x.path === "calendar") ? ["user.read", "	Calendars.Read"] : ["user.read"],
      };
    },
    loginFailedRoute: "./login-failed",
  };
}

Конфигурации для приложений Angular с автономными компонентами

Приложения Angular, использующие автономные компоненты, могут использовать фабричные провайдеры, как показано выше в файле app.config.ts, который затем импортируется в main.ts для начальной загрузки.

Ознакомьтесь с примером автономной версии Angular для использования.

// app.config.ts
import { ApplicationConfig, importProvidersFrom } from "@angular/core";
import { provideRouter } from "@angular/router";
import { routes } from "./app.routes";
import { BrowserModule } from "@angular/platform-browser";
import { provideHttpClient, withInterceptorsFromDi, HTTP_INTERCEPTORS, withFetch, withInterceptors } from "@angular/common/http";
import { provideNoopAnimations } from "@angular/platform-browser/animations";
import { IPublicClientApplication, PublicClientApplication, InteractionType, BrowserCacheLocation, LogLevel } from "@azure/msal-browser";
import { MsalInterceptor, MSAL_INSTANCE, MsalInterceptorConfiguration, MsalGuardConfiguration, MSAL_GUARD_CONFIG, MSAL_INTERCEPTOR_CONFIG, MsalService, MsalGuard, MsalBroadcastService } from "@azure/msal-angular";

export function loggerCallback(logLevel: LogLevel, message: string) {
  console.log(message);
}

export function MSALInstanceFactory(): IPublicClientApplication {
  return new PublicClientApplication({
    auth: {
      clientId: "clientid",
      authority: "https://login.microsoftonline.com/common/",
      redirectUri: "/",
      postLogoutRedirectUri: "/",
    },
    cache: {
      cacheLocation: BrowserCacheLocation.LocalStorage,
    },
    system: {
      allowPlatformBroker: false, // Disables WAM Broker
      loggerOptions: {
        loggerCallback,
        logLevel: LogLevel.Info,
        piiLoggingEnabled: false,
      },
    },
  });
}

export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
  const protectedResourceMap = new Map<string, Array<string>>();
  protectedResourceMap.set("https://graph.microsoft.com/v1.0/me", ["user.read"]);

  return {
    interactionType: InteractionType.Redirect,
    protectedResourceMap,
  };
}

export function MSALGuardConfigFactory(): MsalGuardConfiguration {
  return {
    interactionType: InteractionType.Redirect,
    authRequest: {
      scopes: ["user.read"],
    },
    loginFailedRoute: "/login-failed",
  };
}

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    importProvidersFrom(BrowserModule),
    provideNoopAnimations(),
    provideHttpClient(withInterceptorsFromDi(), withFetch()),
    {
      provide: HTTP_INTERCEPTORS,
      useClass: MsalInterceptor,
      multi: true,
    },
    {
      provide: MSAL_INSTANCE,
      useFactory: MSALInstanceFactory,
    },
    {
      provide: MSAL_GUARD_CONFIG,
      useFactory: MSALGuardConfigFactory,
    },
    {
      provide: MSAL_INTERCEPTOR_CONFIG,
      useFactory: MSALInterceptorConfigFactory,
    },
    MsalService,
    MsalGuard,
    MsalBroadcastService,
  ],
};
// main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import { appConfig } from "./app/app.config";
import { AppComponent } from "./app/app.component";

bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));