Msal Angular 在重定向回应用程序后立即出错

发布于 2025-01-10 22:11:51 字数 5497 浏览 0 评论 0原文

我正在创建一个 Angular 应用程序,该应用程序使用 Azure AD 并使用 msal-Angular 进行身份验证。它工作正常,除了这一关键细节:我打开应用程序,msal 检测到我没有登录,因此它将我重定向到登录页面。我登录并重定向回我的应用程序。但是,我立即收到一个错误(两次,一次接一个),出现类似“

ERROR Error: Uncaught (in promise): BrowserAuthError: no_account_error: No account object provided to acquireTokenSilent and no active account has been set. Please call setActiveAccount or provide an account on the request.
AuthError@http://localhost:4200/vendor.js:22968:24
BrowserAuthError@http://localhost:4200/vendor.js:14464:28
7390/BrowserAuthError</BrowserAuthError.createNoAccountError@http://localhost:4200/vendor.js:14603:16
...

页面仍然大部分损坏,很明显身份验证不起作用”的情况。所以我重新加载页面并修复它。该应用程序显然会记住它已经过身份验证并且一切加载都很好。

我已将日志语句放入应用程序的多个位置,这些位置在初始化和身份验证期间发挥作用,结果发现错误很早就发生,可能在我自己的任何逻辑开始发挥作用之前。这是我在重定向后记录的内容:

Before creating PublicClientApplication // explained later
After creating PublicClientApplication
Initializing application // logged from the constructor of app.module
ERROR ...
[webpack-dev-server] Disconnected!
[webpack-dev-server] Trying to reconnect...
Before creating PublicClientApplication
After creating PublicClientApplication
Initializing application
ERROR ...
[webpack-dev-server] Live Reloading enabled.
msalSubject$ -> msal:loginSuccess // logging events from the MsalBroadcastService.msalSubject$
msalSubject$ -> msal:handleRedirectEnd
redirectObservable -> data (token and account info) // logging events from the MsalService.handleRedirectObservable()
Setting active account // PublicClientApplication.setActiveAccount()
// delay 1 s
// PublicClientApplication.acquireTokenSilent(...)
ERROR: BrowserAuthError: no_account_error: No account object provided to acquireTokenSilent and no active account has been set. Please call setActiveAccount or provide an account on the request.

因此,这会抱怨帐户丢失三次:两次对我来说太早,无法采取任何措施,一次稍后,在我实际上设置了活动帐户之后。

创建 PublicClientApplication 之前/之后的消息是从 app.module 中记录的

let x = console.log('Before creating PublicClientApplication');
const publicClientApplication = new PublicClientApplication({
  auth: {
    clientId: environment.azureAD.clientID,
    authority: environment.azureAD.authority, // This is your tenant ID
    redirectUri: environment.azureAD.redirectURI // This is your redirect URI,
  },
  cache: {
    cacheLocation: 'localStorage',
    storeAuthStateInCookie: isIE, // Set to true for Internet Explorer 11
  }
});
x = console.log('After creating PublicClientApplication');

。总之,前两个错误似乎来自库“内部”的某个地方。有没有人有类似的问题或想法,问题可能是什么?我是否在某个地方犯了根本性错误,应该以不同的方式处理这个问题?这个库的文档很糟糕,而且最近它发生了巨大的变化。网上的许多例子不再适用......

第三个错误也很奇怪。也许第一个错误破坏了库?因为在实际工作的情况下使用相同的逻辑:

作为参考,以下是我刷新页面后的顺序:

[webpack-dev-server] Disconnected!
[webpack-dev-server] Trying to reconnect...
[webpack-dev-server] Live Reloading enabled.
Before creating PublicClientApplication
After creating PublicClientApplication
msalSubject$ -> msal:acquireTokenStart
Initializing application
msalSubject$ -> msal:handleRedirectEnd
redirectObservable -> null
Angular is running in development mode. Call enableProdMode() to enable production mode.
msalSubject$ -> msal:acquireTokenSuccess // This is the event I use to infer login status, because it carries account data
Setting active account
// acquireTokenSilent(...)
... -> successful communication with backend

所以这很好。

以下是我在 AuthenticationService 中所做的操作:

// authentication.service.ts
...
@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {
  login$: Observable<void> = merge(
    this.msalBroadcastService.msalSubject$.pipe( // working part, triggered when already logged in
      filter((msg: EventMessage) => msg.eventType == EventType.ACQUIRE_TOKEN_SUCCESS),
      tap(() => console.log('Msal login successful')),
      // @ts-ignore (even the type definitions are off, this works!)
      filter(result => !!result?.payload?.account),
      tap((result) => {
        // @ts-ignore
        console.log('Setting active account: ', result.payload.account);
        // @ts-ignore
        this.publicClientApplication.setActiveAccount(result.payload.account);
      }),
      map(() => {}),
      tap(() => console.log('Logging in'))
    ),
    this.msalService.handleRedirectObservable().pipe( // broken part, triggered after redirect
      filterFalsy(), // the first thing this emits is null
      tap((res) => console.log('Account info coming in: ', res, 'Setting active account')),
      tap(result => this.publicClientApplication.setActiveAccount(result.account)),
      delay(1000), // just in case, but didn't help
      map(() => {}),
      tap(() => console.log('Logging in too'))
    )
  );
... 
}

此可观察量在本地、类似 Redux 的存储中注册,并触发将标志设置为 true。 AuthService 的 isLoggedIn$() 方法返回此标志的可观察值。

然后,在我的后端服务中,

// backend.service.ts
...
export class BackendService {
...
private readyForConnection$ = this.authenticationService.isLoggedIn$().pipe(
    filterFalsy(),
    take(1),
    tap(() => console.log('isLoggedIn$ has fired. Has the account been set?')),
    switchMapTo(fromPromise(this.publicClientApplication.acquireTokenSilent(this.accessTokenRequest))),
    map(tokens => ({ accessToken: tokens.accessToken }))
  );

我需要这个 readyForConnection$ observable 来验证 WebSocket。只有获得 accessToken 后才能连接。

I am creating an Angular application that uses Azure AD and thus msal-angular for auth purposes. It works fine, except for this one crucial detail: I open the app and msal detects I'm not logged in, so it redirects me to the login page. I log in and am redirected back to my app. However, I immediately get an error (twice, directly after one another), going something like

ERROR Error: Uncaught (in promise): BrowserAuthError: no_account_error: No account object provided to acquireTokenSilent and no active account has been set. Please call setActiveAccount or provide an account on the request.
AuthError@http://localhost:4200/vendor.js:22968:24
BrowserAuthError@http://localhost:4200/vendor.js:14464:28
7390/BrowserAuthError</BrowserAuthError.createNoAccountError@http://localhost:4200/vendor.js:14603:16
...

The page remains largely broken, it is obvious that auth does not work. So I reload the page and that fixes it. The app obviously remembers that it has been authenticated and everyting loads just fine.

I have put logging statements into multiple locations of the app that play a role during initialization and auth, and it turns out that the error occurs very early on, probably before any of my own logic starts to play a role. Here's what I'm logging after the redirect:

Before creating PublicClientApplication // explained later
After creating PublicClientApplication
Initializing application // logged from the constructor of app.module
ERROR ...
[webpack-dev-server] Disconnected!
[webpack-dev-server] Trying to reconnect...
Before creating PublicClientApplication
After creating PublicClientApplication
Initializing application
ERROR ...
[webpack-dev-server] Live Reloading enabled.
msalSubject$ -> msal:loginSuccess // logging events from the MsalBroadcastService.msalSubject$
msalSubject$ -> msal:handleRedirectEnd
redirectObservable -> data (token and account info) // logging events from the MsalService.handleRedirectObservable()
Setting active account // PublicClientApplication.setActiveAccount()
// delay 1 s
// PublicClientApplication.acquireTokenSilent(...)
ERROR: BrowserAuthError: no_account_error: No account object provided to acquireTokenSilent and no active account has been set. Please call setActiveAccount or provide an account on the request.

So this complains about a missing account three times: Two times too early for me to do anything about it, and once later, after I have in fact set the active account.

The messages Before/After creating PublicClientApplication are logged from within app.module

let x = console.log('Before creating PublicClientApplication');
const publicClientApplication = new PublicClientApplication({
  auth: {
    clientId: environment.azureAD.clientID,
    authority: environment.azureAD.authority, // This is your tenant ID
    redirectUri: environment.azureAD.redirectURI // This is your redirect URI,
  },
  cache: {
    cacheLocation: 'localStorage',
    storeAuthStateInCookie: isIE, // Set to true for Internet Explorer 11
  }
});
x = console.log('After creating PublicClientApplication');

In summary, the first two errors seem to come from somewhere "inside" the library. Has anyone had similar issues or an idea, what the problem might be? Have I made a fundamental mistake somewhere and should approach this differently? Documentation of this library is terrible and it has changed drastically in recent times. Many examples online don't apply anymore...

The third error is strange, too. Maybe the first error broke the lib? Because the same logic is used in cases when it actually works:

For reference, here is how the sequence goes after I refresh the page:

[webpack-dev-server] Disconnected!
[webpack-dev-server] Trying to reconnect...
[webpack-dev-server] Live Reloading enabled.
Before creating PublicClientApplication
After creating PublicClientApplication
msalSubject$ -> msal:acquireTokenStart
Initializing application
msalSubject$ -> msal:handleRedirectEnd
redirectObservable -> null
Angular is running in development mode. Call enableProdMode() to enable production mode.
msalSubject$ -> msal:acquireTokenSuccess // This is the event I use to infer login status, because it carries account data
Setting active account
// acquireTokenSilent(...)
... -> successful communication with backend

So that's just fine.

Here's what I do in my AuthenticationService:

// authentication.service.ts
...
@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {
  login$: Observable<void> = merge(
    this.msalBroadcastService.msalSubject$.pipe( // working part, triggered when already logged in
      filter((msg: EventMessage) => msg.eventType == EventType.ACQUIRE_TOKEN_SUCCESS),
      tap(() => console.log('Msal login successful')),
      // @ts-ignore (even the type definitions are off, this works!)
      filter(result => !!result?.payload?.account),
      tap((result) => {
        // @ts-ignore
        console.log('Setting active account: ', result.payload.account);
        // @ts-ignore
        this.publicClientApplication.setActiveAccount(result.payload.account);
      }),
      map(() => {}),
      tap(() => console.log('Logging in'))
    ),
    this.msalService.handleRedirectObservable().pipe( // broken part, triggered after redirect
      filterFalsy(), // the first thing this emits is null
      tap((res) => console.log('Account info coming in: ', res, 'Setting active account')),
      tap(result => this.publicClientApplication.setActiveAccount(result.account)),
      delay(1000), // just in case, but didn't help
      map(() => {}),
      tap(() => console.log('Logging in too'))
    )
  );
... 
}

This observable is registered in a local, Redux-like store and triggers setting a flag to true. The AuthService's isLoggedIn$() method returns an observable of this flag.

And then, in my backend service

// backend.service.ts
...
export class BackendService {
...
private readyForConnection$ = this.authenticationService.isLoggedIn$().pipe(
    filterFalsy(),
    take(1),
    tap(() => console.log('isLoggedIn$ has fired. Has the account been set?')),
    switchMapTo(fromPromise(this.publicClientApplication.acquireTokenSilent(this.accessTokenRequest))),
    map(tokens => ({ accessToken: tokens.accessToken }))
  );

I need this readyForConnection$ observable to authenticate a WebSocket. I can only connect once I have the accessToken.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文