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