ค้นหาเว็บไซต์

typescript - MSAL angular เข้าสู่ระบบหลายการเปลี่ยนเส้นทาง


ผมกำลังพยายามใช้ MSALv2 สำหรับแอป Angular 14 ของผม ผมทำตามคู่มือบนหน้าของ Microsoft สำหรับ MSAL การล็อกอินและล็อกเอาต์ทำงานได้ดี แต่ทุกครั้งที่ผมล็อกอิน ผมจะถูกส่งไปยังหน้าล็อกอินอย่างน้อย 2 ครั้ง จากนั้นจึงไปยังเส้นทางก่อนหน้า ตัวอย่าง:

  • ผู้ใช้ไม่ได้เข้าสู่ระบบแต่ต้องการเข้าถึง /profile/edit/123
  • แทนที่พวกเขาจะ ถูกนำไปยัง "หน้าลงชื่อเข้าใช้ท้องถิ่น" พร้อมข้อมูลบางอย่างเกี่ยวกับกระบวนการลงชื่อเข้าใช้และเหตุผลที่พวกเขาไม่มีสิทธิ์เข้าใช้งาน
  • เมื่อคลิกเข้าสู่ระบบ (โดยไม่กรอกข้อมูลรับรอง) พวกเขาจะถูกเปลี่ยนเส้นทางไปยัง MS ซึ่งการเข้าสู่ระบบจะถูกจัดการ
  • เมื่อเข้าสู่ระบบสำเร็จ พวกเขาจะถูกส่งกลับไปยังเส้นทางก่อนหน้า (/profile/edit/123)

ทั้งหมดนี้ใช้ได้ แต่ในขั้นตอนสุดท้าย พวกเขาจะถูกส่งต่อไปยังหน้าล็อกอินสองครั้ง ก่อนที่จะถูกนำทางไปยังหน้าจริงที่พวกเขาเคยดูมาก่อน นี่คือตารางการตั้งค่าที่เกี่ยวข้องของฉัน:

// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS, HttpClientModule } from "@angular/common/http"; // Import 

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 { LoginComponent} from './login/login.component';

import { MsalModule, MsalRedirectComponent, MsalGuard, MsalInterceptor } from '@azure/msal-angular'; // Import MsalInterceptor
import { InteractionType, PublicClientApplication } from '@azure/msal-browser';

const isIE = window.navigator.userAgent.indexOf('MSIE ') > -1 || window.navigator.userAgent.indexOf('Trident/') > -1;

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    ProfileComponent,
    LoginComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    MatButtonModule,
    MatToolbarModule,
    MatListModule,
    HttpClientModule,
    MsalModule.forRoot( new PublicClientApplication({
      auth: {
        clientId: 'Enter_the_Application_Id_Here',
        authority: 'Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here',
        redirectUri: 'Enter_the_Redirect_Uri_Here',
      },
      cache: {
        cacheLocation: 'localStorage',
        storeAuthStateInCookie: isIE,
      }
    }), {
      interactionType: InteractionType.Redirect,
      authRequest: {
        scopes: ['user.read']
        }
    }, {
      interactionType: InteractionType.Redirect, // MSAL Interceptor Configuration
      protectedResourceMap: new Map([ 
          ['Enter_the_Graph_Endpoint_Here/v1.0/me', ['user.read']]
      ])
    })
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: MsalInterceptor,
      multi: true
    },
    MsalGuard
  ],
  bootstrap: [AppComponent, MsalRedirectComponent]
})
export class AppModule { }
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MsalGuard } from '@azure/msal-angular';
import { CanActivateGuard } from './can-activate.guard';
import { HomeComponent } from './home/home.component';
import { LoginComponent } from './login/login.component';

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  {
    path: 'home',
    component: HomeComponent,
    canActivate: [CanActivateGuard],
  },
  {
    path: 'profiles',
    canActivate: [CanActivateGuard],
    loadChildren: () =>
      import('./profiles/profiles.module').then((m) => m.ProfilesModule),
  }
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      initialNavigation: 'enabledNonBlocking',
    }),
  ],
  exports: [RouterModule],
})
export class AppRoutingModule {}

// can-activate.guard.ts
import { Injectable } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  CanActivate,
  Router,
  RouterStateSnapshot,
} from '@angular/router';
import { MsalBroadcastService, MsalService } from '@azure/msal-angular';
import { InteractionStatus } from '@azure/msal-browser';
import { filter, Observable, of, switchMap } from 'rxjs';
import { LoginService } from './services/login.service';

@Injectable({ providedIn: 'root' })
export class CanActivateGuard implements CanActivate {
  constructor(
    private msalService: MsalService,
    private loginService: LoginService,
    private router: Router,
    private msalBroadcastService: MsalBroadcastService
  ) {}

  canActivate(
    _route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | boolean {
    return this.msalBroadcastService.inProgress$.pipe(
      filter((status: InteractionStatus) => status === InteractionStatus.None),
      switchMap(() => {
        if (this.msalService.instance.getAllAccounts().length > 0) {
          return of(true);
        }
        this.loginService.redirectUrl = state.url;

        return of(false);
      })
    );
  }
}

// login.service.ts
import { Inject, Injectable, OnInit } from '@angular/core';
import {
  MsalService,
  MsalBroadcastService,
  MSAL_GUARD_CONFIG,
  MsalGuardConfiguration,
} from '@azure/msal-angular';
import {
  AccountInfo,
  AuthenticationResult,
  InteractionStatus,
  InteractionType,
  PopupRequest,
  RedirectRequest,
  EventMessage,
  EventType,
} from '@azure/msal-browser';
import { Observable, of, Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class LoginService implements OnInit {
  loggedIn!: boolean;
  redirectUrl: string = '';
  private readonly _destroying$ = new Subject<void>();

  constructor(
    @Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration,
    private authService: MsalService,
    private msalBroadcastService: MsalBroadcastService
  ) {}
  ngOnInit(): void {
    this.msalBroadcastService.msalSubject$
      .pipe(
        filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS)
      )
      .subscribe((result: EventMessage) => {
        console.log(result);
      });
  }

  updateLoggedInStatus() {
    this.msalBroadcastService.inProgress$
      .pipe(
        filter(
          (status: InteractionStatus) => status === InteractionStatus.None
        ),
        takeUntil(this._destroying$)
      )
      .subscribe(() => {
        this.setLoggedIn();
        this.checkAndSetActiveAccount();
      });
  }

  login() {
    if (this.msalGuardConfig.interactionType === InteractionType.Popup) {
      this.loginWithPopup();
    } else {
      this.loginWithRedirect();
    }
  }

  logout() {
    this.authService.logout();
  }

  destroy() {
    this._destroying$.next(undefined);
    this._destroying$.complete();
  }
  
  isLoggedIn(): boolean {
    return this.loggedIn;
  }

  getActiveAccount(): AccountInfo | null {
    return this.authService.instance.getActiveAccount();
  }

  private checkAndSetActiveAccount() {
    /**
     * If no active account set but there are accounts signed in, sets first account to active account
     * To use active account set here, subscribe to inProgress$ first in your component
     * Note: Basic usage demonstrated. Your app may require more complicated account selection logic
     */
    let activeAccount = this.authService.instance.getActiveAccount();

    if (
      !activeAccount &&
      this.authService.instance.getAllAccounts().length > 0
    ) {
      let accounts = this.authService.instance.getAllAccounts();
      this.authService.instance.setActiveAccount(accounts[0]);
    }
  }

  private setLoggedIn() {
    this.loggedIn = this.authService.instance.getAllAccounts().length > 0;
  }

  private loginWithPopup() {
    if (this.msalGuardConfig.authRequest) {
      this.authService
        .loginPopup({ ...this.msalGuardConfig.authRequest } as PopupRequest)
        .subscribe((response: AuthenticationResult) => {
          this.authService.instance.setActiveAccount(response.account);
        });
    } else {
      this.authService
        .loginPopup()
        .subscribe((response: AuthenticationResult) => {
          this.authService.instance.setActiveAccount(response.account);
        });
    }
  }

  private loginWithRedirect() {
    if (this.msalGuardConfig.authRequest) {
      console.log('WILL REDIRECT: ', this.redirectUrl);
      this.authService.loginRedirect({
        redirectStartPage: this.redirectUrl,
        ...this.msalGuardConfig.authRequest,
      } as RedirectRequest);
    } else {
      this.authService.loginRedirect();
    }
  }
}