aspnetcore: MSAL on Blazor WebAssembly fails to initiate sign-in when an invalid_grant or AADSTS700081 error occurs--as in when the refresh token is expired

Describe the bug

MSAL on Blazor WebAssembly fails to initiate sign-in when an invalid_grant or AADSTS700081 error occurs–as in when the refresh token is expired

To Reproduce

My MSAL on the client is configured as:

            builder.Services.AddMsalAuthentication(options =>
            {
                builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
                options.ProviderOptions.Cache.CacheLocation = "localStorage";
                options.ProviderOptions.DefaultAccessTokenScopes.Add(
                    builder.Configuration["AzureAd:MyScopeId"]);
                options.UserOptions.RoleClaim = "roles";            
            });

I sign in to my Blazor Web Assembly app, then wait till my refresh token expires (for me, 1 day). Then I try to refresh the page, which includes a component like this:

      <AuthorizeView>
        <Authorized>
          <span>@context.User.UserId()</span>
        </Authorized>
        <Authorizing>
            Authorizing
        </Authorizing>
      </AuthorizeView>

Expected behavior

The page should show “Authorizing”, then the code in MSAL that AuthorizeView triggers should automatically initiate a redirect to sign-in, so that the user can go through authentication and thus get a new refresh token and ID token. (Once signed in, the user should redirect back to the same page, which should show the content within the <Authorized> fragment.)

Actual behavior

The page shows “Authorizing”, and the HTTP request POST https://login.microsoftonline.com/0c33cce8-883c-4ba5-b615-34a6e2b8ff38/oauth2/v2.0/token returns HTTP 400 with

    error "invalid_grant"
    error_description "AADSTS700081:  The refresh token has expired due to maximum lifetime. The token was  issued on 2020-11-24T12:56:15.5198672+00:00 and the maximum allowed  lifetime for this application is 1.00:00:00.\r\nTrace ID:  c4360626-5489-4009-89ad-5ae02bd0ca00\r\nCorrelation ID:  228a7671-3752-4ca9-bf1f-7c0c51368fb6\r\nTimestamp:     

Then Blazor allows an exception to be thrown with Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: login_required: AADSTS50058: A silent sign-in request was sent but no user is signed in. and further detail. The error is written to the browser console and Blazor shows the standard “An unhandled error has occurred. Reload” bottom banner.`

Possible Solution

Isn’t there some way to configure MSAL to initiate the interactive sign-in process on invalid_grant, rather than having it fail fatally? Or is this just a big bug? Any such action would have to navigate/redirect or popup on the user’s browsing page, not naively redirect an XHR request, of course.

This seems to be similar to: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/2219 , though I am not using MSAL.js directly.

This looks like my situation exactly, but I don’t see how I can mitigate. My code never explicitly calls AcquireTokenSilent.

Additional context/ Logs / Screenshots

Here’s the end of the stack trace: https://gist.github.com/szalapski/942baf9b8da7b5bdb68ebd7f9e2f5544

(Thought I’d post first on MSAL repo, but they say it is a aspnetcore issue.)

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 18 (8 by maintainers)

Most upvoted comments

Great, I got the workaround working. In case anyone else wants to do the same, here is the code:

Part of MainLayout.razor:

@inject IJSRuntime JS
@* ... *@
     <AuthorizeView>
        <NotAuthorized>
           @* ... *@
        </NotAuthorized>
        <Authorized>
           @* ... *@
        </Authorized>
        <Authorizing>
          @* Mitigation for https://github.com/dotnet/aspnetcore/issues/28151 - can remove when fixed*@
          <div class="m-4 authentication-unhandled-failure-message">
            If this gets stuck, just
            <a class="cursor-pointer" @onclick="RefreshTokenExpiredFailureWorkaround">sign in again</a>.
          </div>
        </Authorizing>
      </AuthorizeView>

@code
{
  // see also https://github.com/dotnet/aspnetcore/issues/28151
  private async Task RefreshTokenExpiredFailureWorkaround() =>
    await JS.InvokeVoidAsync("window.AuthenticationService.signIn");
}

CSS:

/* Mitigation for https://github.com/dotnet/aspnetcore/issues/28151 - can remove when fixed*/
.authentication-unhandled-failure-message {
    // Since we cannot detect some errors in authenticating, using animation to show after a delay
    animation: fade-in 5s step-end;
    animation-fill-mode: both;
    opacity: 0;
}
.cursor-pointer {
    cursor: pointer;
}

@szalapski Glad the workaround resolved the issue.

A fix for this will be shipped in 5.0.2.

@szalapski It applies to any scenario where the GetUser API so it shouldn’t affect the error handling you’re using around HTTP requests.

Update: I see where the issue is. We don’t have any error handling in the token acquisition in our getUser method which causes exceptions that bubble up from it to be fatal.

We didn’t catch this error in our validations/testing because we don’t run into the experienced tokens often based on our test scripts and dev loops.

The fix here is to add some error handling to the getUser method.

@captainsafia can you please handle this? Let’s target 5.0.2.