You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 的完整功能。

已完成的操作:

  1. 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"
  1. 创建 public/auth-redirect.html 文件:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Auth redirect</title>
</head>
<body>
</body>
</html>
  1. 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()),
  ],
};
  1. 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,
};
  1. 认证组件模板 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>
  1. 组件样式 src/app/azure/azure-auth-panel.scss:
:host {
  display: inline-flex;
  flex-flow: row nowrap;
  gap: 1em;
}
  1. 组件逻辑 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();
  }
}
  1. 认证服务 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();
  }
}
  1. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.04 11:15:54