使用 RemoteAuthenticatorView OnLogInSucceeded 提供第二个 AzureAD 登录对话框

Using RemoteAuthenticatorView OnLogInSucceeded gives a second AzureAD login dialog box

很难理解发生了什么,我真的可以使用一些想法和输入。拜托,我不太擅长用文字描述问题是什么,所以请温柔点。

我有一个 Blazor wasm 前端和一个 Azure Functions 后端。
我做一个标准的,没什么特别的,Azure AD 登录。 稍后在代码中,我使用不记名令牌连接到 AzFunction,并且它起作用了。 AzFunction 评估令牌并识别用户以进行进一步处理。我应该提到 AzFunction 连接到 Graph 和其他 API。

一切正常,正常工作,这是我的问题。
我想在登录时连接到 AzFuntion,以获取一些用户配置文件信息。为了调试,我简单地制作了一个按钮,再次,一切正常。

然后我将“按钮代码”移动到 RemoteAuthenticatorView OnLogInSucceeded 调用的方法,在 Authenticator.razor。

<RemoteAuthenticatorView Action="@Action" LogInFailed="LogInFailedFragment" OnLogInSucceeded="LoginComplete" />

这是我的问题: 使用 OnLogInSucceeded 我获得了 2 个我必须响应的 Azure AD 登录。在第一个之后,它直接进入第二个。一切都只是因为我将代码从按钮移到了 OnLogInSucceeded。当我调试时,我清楚地看到令牌在连接到 AzFunction 之前就已经存在。

此外,当我在从 OnLogInSucceeded 调用的 LoginComplete 函数中 visual studio 设置断点并保持几秒钟时,它只通过一个登录对话框完成登录过程。

谁能帮我理解为什么?

任何关于更好的指针请放入“获取用户配置文件”代码?我需要的是 运行 的代码,因此当登录完成后,只需一次登录即可检索用户个人资料信息。

编辑:
代码似乎有更好的解决方案,但我仍然不明白是什么让第二次登录应用程序......这是我的问题的主要原因。

感谢扎克 , I was sent in a good direction and read Microsoft pages on AuthenticationStateChanged Event。它还简化了我的代码,因为我现在在 UserProfile 组件中检索了 UserProfile,而不是 Authentication.razor。

@inject AuthenticationStateProvider AuthenticationStateProvider

private void OnAuthenticationStateChanged(Task<AuthenticationState> task)
{
    if(task.IsCompletedSuccessfully)
    {
        GetUserProfile();
    }
}

不确定这是否是“最终代码”,但它确实有效并且在我看来是一种合理的方式。

好吧,感谢 Raymond,我也得到了很好的指导,并制作了一个组件来加载此类数据。当组件初始化时,我必须结合事件和加载。我仍然觉得 M$ 应该并且可能会找到更好的方法来做到这一点。

对于此应用程序,我使用 Azure B2C 进行身份验证,并且我使用重定向而不是弹出窗口。当我初始化用户状态时,我调用我的 api 获取用户配置文件,或者如果它是新用户则创建一个。

InitialDataLoader.razor

@implements IDisposable
@inject NavigationManager _navigationManager
@inject UserState _userState
@inject AuthenticationStateProvider _authenticationStateProvider

@ChildContent

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    // The event can trigger multiple times when being redirected from the Authentication page to wherever you're going.
    // So we use this to check.
    public bool hasRunInit;

    protected override async Task OnInitializedAsync()
    {
        Console.WriteLine(DateTime.Now.ToString("hh:mm:ss:fff") + " InitialDataLoader -> OnInitializedAsync -> " + _navigationManager.Uri);

        // This component is already loaded and initialized on the Authentication page, so we have to subscribe to this event to
        // check when the user bounces back from Azure B2C and gets logged in. In that case, we do the initial load in on the event
        _authenticationStateProvider.AuthenticationStateChanged += this.OnAuthenticationStateChanged;


        // If the user is authenticated and reloads the page in the browser, the event won't trigger so we can do the initial load here.
        var user = (await _authenticationStateProvider.GetAuthenticationStateAsync()).User;
        Console.WriteLine(DateTime.Now.ToString("hh:mm:ss:fff") + " InitialDataLoader -> OnInitializedAsync -> IsUserAuthenticated: " + user.Identity.IsAuthenticated);
        Console.WriteLine(DateTime.Now.ToString("hh:mm:ss:fff") + " InitialDataLoader -> OnInitializedAsync -> IsStateInitialized: " + _userState.IsInitialized);

        if (user.Identity.IsAuthenticated && !_userState.IsInitialized)
        {
            hasRunInit = true;
            Console.WriteLine(DateTime.Now.ToString("hh:mm:ss:fff") + " InitialDataLoader -> OnInitializedAsync -> InitUser");
            await _userState.InitializeAsync(this);
        }
    }

    void IDisposable.Dispose()
    {
        _authenticationStateProvider.AuthenticationStateChanged -= this.OnAuthenticationStateChanged;
    }

    private async void OnAuthenticationStateChanged(Task<AuthenticationState> task)
    {
        Console.WriteLine(DateTime.Now.ToString("hh:mm:ss:fff") + " InitialDataLoader -> OnAuthenticationStateChanged -> " + _navigationManager.Uri);

        var user = (await task).User;
        Console.WriteLine(DateTime.Now.ToString("hh:mm:ss:fff") + " InitialDataLoader -> OnAuthenticationStateChanged -> IsUserAuthenticated: " + user.Identity.IsAuthenticated);
        Console.WriteLine(DateTime.Now.ToString("hh:mm:ss:fff") + " InitialDataLoader -> OnAuthenticationStateChanged -> IsStateInitialized: " + _userState.IsInitialized);

        if (user.Identity.IsAuthenticated && !_userState.IsInitialized)
        {
            if (!hasRunInit)
            {
                hasRunInit = true;

                Console.WriteLine(DateTime.Now.ToString("hh:mm:ss:fff") + " CascadingAppStateProvider -> OnInitializedAsync -> InitUser");
                await _userState.InitializeAsync(this);
            }
            else
            {
                Console.WriteLine(DateTime.Now.ToString("hh:mm:ss:fff") + " InitialDataLoader -> OnInitializedAsync -> Init has already been triggered!");
            }
        }
    }
}

App.razor

<CascadingAuthenticationState>

    <InitialDataLoader>

        <Router AppAssembly="@typeof(Program).Assembly">
            <Found Context="routeData">
                <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                    <Authorizing>
                        ------------- Authorizing.. ------------------
                    </Authorizing>
                    <NotAuthorized>
                        @if (!context.User.Identity.IsAuthenticated)
                        {
                            <RedirectToLogin />
                        }
                        else
                        {
                            <p>You are not authorized to access this resource.</p>
                        }
                    </NotAuthorized>
                </AuthorizeRouteView>
            </Found>
            <NotFound>
                <LayoutView Layout="@typeof(MainLayout)">
                    <p>Sorry, there's nothing at this address.</p>
                </LayoutView>
            </NotFound>
        </Router>

    </InitialDataLoader>

</CascadingAuthenticationState>

当用户从网关弹回时,组件会在应用程序对用户进行身份验证之前进行初始化。然后该事件触发两次,一次用于身份验证页面,一次用于您正在查看的任何页面。