Utilização de redirecionamentos no MSAL Angular

Ao usar redirecionamentos com o MSAL, é obrigatório tratar os redirecionamentos com o MsalRedirectComponent ou o handleRedirectObservable.

Tenha em atenção que foram adicionadas mais abaixo orientações específicas sobre a utilização do MSAL Angular com componentes autónomos do Angular.

  1. MsalRedirectComponent
  2. Subscrever-se manualmente handleRedirectObservable
  3. Redirecionamentos com componentes autónomos

1. MsalRedirectComponent: Um componente dedicado handleRedirectObservable

Note

Esta abordagem não é compatível com componentes independentes do Angular. Consulte a secção sobre redirecionamentos com componentes autónomos abaixo para mais orientações.

Esta é a nossa abordagem recomendada para lidar com redirecionamentos:

  • @azure/msal-angular fornece um componente dedicado de redirecionamento que pode ser importado para a sua aplicação. Recomendamos importar o MsalRedirectComponent e inicializá-lo juntamente com AppComponent na sua aplicação, ao nível do app.module.ts, pois isto tratará de todos os redirecionamentos sem que os seus componentes precisem de subscrever manualmente handleRedirectObservable().
  • Páginas que pretendam executar funções após redirecionamentos (por exemplo, funções de conta de utilizador, alterações na interface, etc.) devem subscrever o inProgress$ observável, filtrando para InteractionStatus.None. Isto garantirá que não haja interações em curso durante a execução das funções. Tenha em atenção que o último / mais recente InteractionStatus também estará disponível ao subscrever o observável inProgress$. Por favor, consulte a nossa documentação sobre eventos para mais informações sobre como verificar interações.
  • Se não quiser usar o MsalRedirectComponent, deve gerir você mesmo os redirecionamentos com handleRedirectObservable(), conforme indicado na abordagem abaixo.
  • Consulte o nosso exemplo de módulos Angular para um exemplo desta abordagem.

msal.redirect.component.ts

// This component is part of @azure/msal-angular and can be imported and bootstrapped
import { Component, OnInit } from "@angular/core";
import { MsalService } from "./msal.service.ts";

@Component({
  selector: 'app-redirect', // Selector to be added to index.html
  template: ''
})
export class MsalRedirectComponent implements OnInit {
  
  constructor(private authService: MsalService) { }
  
  ngOnInit(): void {    
      this.authService.handleRedirectObservable().subscribe();
  }
  
}

index.html

<body>
  <app-root></app-root>
  <app-redirect></app-redirect> <!-- Selector for additional bootstrapped component -->
</body>

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 { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { IPublicClientApplication, PublicClientApplication, InteractionType, BrowserCacheLocation, LogLevel } from '@azure/msal-browser';
import { MsalGuard, MsalInterceptor, MsalBroadcastService, MsalInterceptorConfiguration, MsalModule, MsalService, MSAL_GUARD_CONFIG, MSAL_INSTANCE, MSAL_INTERCEPTOR_CONFIG, MsalGuardConfiguration, MsalRedirectComponent } from '@azure/msal-angular'; // Redirect component imported from msal-angular

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

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,
    },
    system: {
      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 };
}

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    ProfileComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    MatButtonModule,
    MatToolbarModule,
    MatListModule,
    HttpClientModule,
    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
    },
    MsalService,
    MsalGuard,
    MsalBroadcastService
  ],
  bootstrap: [AppComponent, MsalRedirectComponent] // Redirect component bootstrapped here
})
export class AppModule { }

app.component.ts

import { Component, OnInit, Inject, OnDestroy } from '@angular/core';
import { MsalBroadcastService, InteractionStatus } from '@azure/msal-angular';
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(
        filter((status: InteractionStatus) => status === InteractionStatus.None),
        takeUntil(this._destroying$)
      )
      .subscribe(() => {
        // Do user account/UI functions here
      })
  }

2. Subscrever a handleRedirectObservable manualmente

Esta não é a nossa abordagem recomendada, mas se não conseguir iniciar o MsalRedirectComponent, deve tratar dos redirecionamentos da handleRedirectObservable seguinte forma:

  • handleRedirectObservable() deve ser subscrito em todas as páginas para as quais possa ocorrer um redirecionamento. As páginas protegidas pelo MSAL Guard não precisam de subscrever o handleRedirectObservable(), pois os redirecionamentos são processados pelo Guard.
  • Não se deve aceder nem realizar qualquer ação relacionada com contas de utilizador enquanto handleRedirectObservable() não estiver concluído, pois poderá não estar ainda completamente preenchido até lá. Além disso, se APIs interativas forem chamadas enquanto handleRedirectObservables() está em curso, resultará num interaction_in_progress erro. Consulte o nosso documento sobre eventos para mais informações sobre verificação de interações e o nosso documento sobre erros para detalhes sobre o interaction_in_progress erro.
  • Consulte o nosso exemplo de módulos MSAL Angular para exemplos desta abordagem.

Exemplo de ficheiro home.component.ts:

import { Component, OnInit } from '@angular/core';
import { MsalBroadcastService, MsalService } from '@azure/msal-angular';
import { AuthenticationResult } from '@azure/msal-browser';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  constructor(private authService: MsalService) { }

  ngOnInit(): void {
    this.authService.handleRedirectObservable().subscribe({
      next: (result: AuthenticationResult) => {
        // Perform actions related to user accounts here
      },
      error: (error) => console.log(error)
    });
  }

}

handleRedirectObservable Opções

handleRedirectObservable aceita um objeto opcional HandleRedirectPromiseOptions com as seguintes propriedades:

Property Tipo Description
hash string Hash opcional para processar em vez do hash da URL atual.
navigateToLoginRequestUrl boolean Se deve navegar até ao URL original do pedido após processar o redirecionamento. O valor padrão é true. Define para false se quiseres tratar da navegação tu próprio.

Exemplo de utilização:

// Basic usage - processes redirect and navigates to original URL
this.authService.handleRedirectObservable().subscribe();

// Disable automatic navigation after redirect
this.authService.handleRedirectObservable({ navigateToLoginRequestUrl: false }).subscribe({
  next: (result: AuthenticationResult) => {
    if (result) {
      // Handle navigation yourself
      this.router.navigate(['/home']);
    }
  }
});

// Process a specific hash
this.authService.handleRedirectObservable({ hash: '#code=...' }).subscribe();

Note

Passar uma cadeia de hash diretamente para handleRedirectObservable(hash) está obsoleto. Use o objeto options em vez disso: handleRedirectObservable({ hash: "#..." }).

3. Redirecionamentos com componentes autónomos

Como muitas aplicações Angular que utilizam componentes autónomos não conseguem inicializar o MsalRedirectComponent, é necessário subscrever diretamente o handleRedirectObservable. A nossa recomendação é subscrevê-lo no ficheiro app.component.ts.

  • Dependendo da arquitetura da sua aplicação, pode ter de subscrever handleRedirectObservable() noutras áreas também.
  • A verificação de interações em curso ainda se aplica, por favor consulte o nosso documento sobre eventos para mais informações sobre como verificar interações.
  • Consulte o nosso exemplo independente do Angular para exemplos desta abordagem.

Exemplo de app.component.ts ficheiro:

import { Component, OnInit, Inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css'],
    standalone: true,
    imports: [CommonModule, RouterModule]
})
export class AppComponent implements OnInit {

  constructor(
    @Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration,
    private authService: MsalService,
    private msalBroadcastService: MsalBroadcastService
  ) {}

  ngOnInit(): void {
    this.authService.handleRedirectObservable().subscribe();
  }
}