Configurazione di MSAL Angular

MSAL per Angular può essere configurato in diversi modi. Questo articolo descrive le opzioni di configurazione disponibili per MSAL Angular, inclusi gli approcci statici e dinamici e fornisce esempi di codice per l'integrazione dell'autenticazione nell'app. Usare questa guida per selezionare il metodo di configurazione più adatto ai requisiti dell'applicazione e offre un'esperienza di accesso ottimale per gli utenti.

Opzioni di configurazione

@azure/msal-angular accetta tre oggetti di configurazione:

  1. Configurazione: si tratta dello stesso oggetto di configurazione usato per la libreria principale @azure/msal-browser . Tutte le opzioni di configurazione sono disponibili qui.
  2. MsalGuardConfiguration: set di opzioni specificamente per angular guard.
  3. MsalInterceptorConfiguration: set di opzioni specifico per l'intercettore Angular.

Configurazioni specifiche di Angular

  • interactionType deve essere specificato in MsalGuardConfiguration e MsalInterceptorConfiguration e può essere impostato su Popup o Redirect.
  • L'oggetto protectedResourceMap su MsalInterceptorConfiguration viene utilizzato per proteggere le route.
  • È possibile specificare un oggetto facoltativo authRequest su MsalGuardConfiguration e MsalInterceptorConfiguration per impostare opzioni aggiuntive.
  • È possibile impostare una stringa facoltativa loginFailedRoute in MsalGuardConfiguration. Msal Guard reindirizzerà a questo percorso se è richiesto l'accesso e questo non va a buon fine.

Per altre informazioni su configurazioni, utilizzo e differenze con MSAL Angular v1, vedere la documentazione di MsalInterceptor e MsalGuard .

Configurazione per i reindirizzamenti

Si consiglia di importare MsalRedirectComponent ed eseguire l'inizializzazione con AppComponent se si intende utilizzare i reindirizzamenti. Per altri dettagli, vedere la documentazione di reindirizzamento .

Nota: A partire da MSAL v3.x, l'inizializzazione dell'oggetto applicazione è ora necessaria. Per altri dettagli, vedere la guida all'aggiornamento della versione 2-v3 . Per informazioni dettagliate sull'aggiornamento a MSAL Angular v5, vedere la guida all'aggiornamento di v4-v5.

MsalModule.forRoot

La MsalModule classe contiene un metodo statico che può essere chiamato nel app.module.ts file:

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

Provider di factory

È possibile fornire anche le opzioni di configurazione tramite i factory provider.

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

Se è necessario configurare in modo dinamico MSAL Angular (ad esempio, in base ai valori restituiti da un'API), è possibile usare platformBrowserDynamic. platformBrowserDynamic è la factory della piattaforma, usata per inizializzare l'applicazione, ed è in grado di accettare opzioni di configurazione. platformBrowserDynamic deve essere già presente quando l'applicazione Angular è configurata.

Di seguito è riportato un esempio di come configurare @azure/msal-angular dinamicamente con platformBrowserDynamic e un file 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"]]]
  }
}

Configurazioni dinamiche con Factory Provider e APP_INITIALIZER

Per configurare dinamicamente MSAL Angular, è possibile usare i provider di factory con 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 - Richiesta di autenticazione dinamica

MsalGuard consente anche di modificare dinamicamente authRequest in fase di esecuzione. In questo modo è possibile scegliere un'autorità diversa per una route o aggiungere in modo dinamico gli ambiti in base a 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",
  };
}

Configurazioni per le app Angular con componenti autonomi

Le applicazioni Angular che utilizzano componenti autonomi possono utilizzare provider factory, come sopra nel file app.config.ts, che viene quindi importato in main.ts per l’avvio.

Consulta il nostro esempio standalone di Angular per l'utilizzo.

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