Angular v20+中@azure/msal-browser的loginPopup弹窗无法关闭求助
Angular v20+ 中使用 @azure/msal-browser 的 loginPopup 弹窗无法关闭问题
我在 Angular v20+ 环境下尝试用 @azure/msal-browser 的 PublicClientApplication 调用 loginPopup 功能,弹窗能正常打开并完成认证,重定向后 location.hash 里也包含授权码,但弹窗始终关不掉,主窗口的 MSAL 无法读取到认证结果。
我的场景:不需要 MSAL 处理主 API 认证或路由保护,仅需通过 Microsoft Graph API 获取特定信息,因此不想使用 @azure/msal-angular 的完整功能。
已完成的操作:
- Angular 应用依赖配置(package.json):
"@angular/cdk": "^21.2.13", "@angular/common": "^21.2.0", "@angular/compiler": "^21.2.0", "@angular/core": "^21.2.0", "@angular/forms": "^21.2.0", "@angular/material": "^21.2.13", "@angular/platform-browser": "^21.2.0", "@angular/router": "^21.2.0", "@azure/msal-browser": "^5.11.0", "rxjs": "~7.8.0", "tslib": "^2.3.0"
- 创建
public/auth-redirect.html文件:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Auth redirect</title> </head> <body> </body> </html>
src/app/app.config.ts配置:
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; import { provideHttpClient, withFetch } from '@angular/common/http'; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), provideRouter(routes), provideHttpClient(withFetch()), ], };
src/app/azure/azure-auth-config.ts配置:
export const azureAuthConfig = { tenantId: '01234567-0123-4567-0123-0123456789ab' as const, clientId: '01234567-0123-4567-0123-0123456789ab' as const, redirectUrl: '/auth-redirect.html' as const, scopes: ['openid', 'profile', 'email', 'offline_access'] as const, };
- 认证组件模板
src/app/azure/azure-auth-panel.html:
<mat-menu #menu="matMenu"> @if (auth.activeAccount()) { <button mat-menu-item (click)="logout()">Logout</button> } @else { <button mat-menu-item (click)="login()">Login</button> } </mat-menu> <button mat-mini-fab [matMenuTriggerFor]="menu"> @if (auth.activeAccount()) { <mat-icon>user</mat-icon> } @else { <mat-icon>login</mat-icon> } </button>
- 组件样式
src/app/azure/azure-auth-panel.scss:
:host { display: inline-flex; flex-flow: row nowrap; gap: 1em; }
- 组件逻辑
src/app/azure/azure-auth-panel.ts:
import { Component, inject } from '@angular/core'; import { MatMiniFabButton } from '@angular/material/button'; import { MatIcon } from '@angular/material/icon'; import { AzureAuthService } from './azure-auth-service'; import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu'; @Component({ selector: 'app-azure-auth-panel', imports: [MatMiniFabButton, MatIcon, MatMenu, MatMenuTrigger, MatMenuItem], templateUrl: './azure-auth-panel.html', styleUrl: './azure-auth-panel.scss', }) export class AzureAuthPanel { protected readonly auth = inject(AzureAuthService); ngOnInit() { this.auth.initialize(); } protected logout() { this.auth.logoutPopup(); } protected login() { this.auth.loginPopup(); } }
- 认证服务
src/app/azure/azure-auth-service.ts:
import { effect, Injectable, signal, untracked } from '@angular/core'; import { AccountInfo, BrowserCacheLocation, EventMessage, IPublicClientApplication, LogLevel, PublicClientApplication, } from '@azure/msal-browser'; import { azureAuthConfig } from './azure-auth-config'; import { from } from 'rxjs'; @Injectable({ providedIn: 'root', }) export class AzureAuthService { protected readonly msalApplication: IPublicClientApplication; protected readonly authRequest = { scopes: [...azureAuthConfig.scopes] }; protected readonly msalEvent = signal<EventMessage | undefined>(undefined); readonly activeAccount = signal<AccountInfo | null>(null); readonly allAccounts = signal<AccountInfo[]>([]); protected readonly msalInitialized = signal(false); constructor() { effect(() => { console.log('MSAL Event:', this.msalEvent()?.eventType ?? 'construct'); untracked(() => { this.allAccounts.set(this.msalApplication.getAllAccounts()); this.activeAccount.set(this.msalApplication.getActiveAccount()); }); }); effect(() => console.dir(this.activeAccount())); this.msalApplication = new PublicClientApplication({ auth: { clientId: azureAuthConfig.clientId, authority: `https://login.microsoftonline.com/${azureAuthConfig.tenantId}`, redirectUri: `${window.location.origin}${azureAuthConfig.redirectUrl}`, }, cache: { cacheLocation: BrowserCacheLocation.LocalStorage }, system: { allowPlatformBroker: false, // Disables WAM Broker loggerOptions: { loggerCallback: (level, message) => console.log(`MSAL Log level ${level}: ${message}`), logLevel: LogLevel.Info, piiLoggingEnabled: false, }, }, }); this.msalApplication.addEventCallback((evt) => this.msalEvent.set(evt)); from(this.msalApplication.clearCache()).subscribe(); } initialize() { if (!this.msalInitialized()) { from(this.msalApplication.initialize()).subscribe(() => this.msalInitialized.set(true)); } else { from(this.msalApplication.handleRedirectPromise()).subscribe((result) => { console.log('Handle Redirect Result:', result); }); } } loginPopup() { from(this.msalApplication.loginPopup(this.authRequest)).subscribe((result) => { console.log('Login Popup Result:', result); this.msalApplication.setActiveAccount(result.account); }); } logoutPopup() { from(this.msalApplication.logoutPopup()).subscribe(); } }
- Azure 租户中已配置应用 Client ID,SPA 重定向 URL 也已设置,认证能成功返回授权码,但 MSAL 无法识别该结果。
解决方案
1. 补全重定向页面的处理逻辑
当前的 auth-redirect.html 是空页面,MSAL 需要在此页面初始化并处理回调,才能通知主窗口关闭弹窗。修改 public/auth-redirect.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Auth redirect</title> <script type="module"> import { PublicClientApplication } from '@azure/msal-browser'; // 配置需与主应用保持一致 const azureAuthConfig = { clientId: '01234567-0123-4567-0123-0123456789ab', tenantId: '01234567-0123-4567-0123-0123456789ab', redirectUri: `${window.location.origin}/auth-redirect.html` }; const msalInstance = new PublicClientApplication({ auth: { clientId: azureAuthConfig.clientId, authority: `https://login.microsoftonline.com/${azureAuthConfig.tenantId}`, redirectUri: azureAuthConfig.redirectUri }, cache: { cacheLocation: 'localStorage' } }); msalInstance.handleRedirectPromise().then(() => { // 处理完成后关闭弹窗 window.close(); }).catch(err => { console.error('Redirect error:', err); window.close(); }); </script> </head> <body> <p>正在处理认证,请稍候...</p> </body> </html>
2. 修正服务的初始化逻辑
在 AzureAuthService 的 initialize 方法中,无论是否已初始化,都需调用 handleRedirectPromise,弹窗认证的回调依赖此方法处理:
initialize() { from(this.msalApplication.initialize()).subscribe(() => { this.msalInitialized.set(true); // 初始化完成后立即处理重定向回调 from(this.msalApplication.handleRedirectPromise()).subscribe((result) => { console.log('Handle Redirect Result:', result); if (result?.account) { this.msalApplication.setActiveAccount(result.account); } }); }); }
3. 优化弹窗请求配置
调用 loginPopup 时显式指定弹窗参数,避免跨窗口通信异常:
loginPopup() { const popupRequest = { ...this.authRequest, popupWindowAttributes: { width: 600, height: 600 } }; from(this.msalApplication.loginPopup(popupRequest)).subscribe((result) => { console.log('Login Popup Result:', result); this.msalApplication.setActiveAccount(result.account); }); }
4. 检查浏览器与配置一致性
- 确保浏览器允许第三方 Cookie 和本地存储,MSAL 依赖这些存储共享认证状态
- 确认 Azure 门户中配置的重定向 URL 与代码中的完全一致(包括协议、域名、路径)
内容的提问来源于stack exchange,提问作者VBobCat
相关产品推荐
相关产品推荐

