user3529977
user3529977

Reputation: 245

Suspected bug in Microsoft Identity Platform with ASP.NET Core 3.1 Razor Pages

I am developing an application to be hosted in the Azure App Services environment which consists of a front-end Web App, a back-end Web API and a SQL Database (using Azure SQL). The front-end Web App is a Razor Pages app. We are trying to use the Microsoft Identity Platform (via Microsoft.Identity.Web and Microsoft.Identity.Web.UI libraries) to acquire an access token for the API when needed.

It works perfectly well the first time, but once a token has been acquired and cached - if the application is restarted it fails with this error:

IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent.
No account or login hint was passed to the AcquireTokenSilent call.

Startup configuration is (I've tried various variants of this):

public void ConfigureServices(IServiceCollection services)
{
    services.AddDistributedMemoryCache();
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
        options.HandleSameSiteCookieCompatibility();
    });
    services.AddOptions();

    services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
        .EnableTokenAcquisitionToCallDownstreamApi(new string[] { Configuration["Api:Scopes"] })
        .AddInMemoryTokenCaches();

    services.AddControllersWithViews(options =>
    {
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    }).AddMicrosoftIdentityUI();

    services.AddRazorPages().AddRazorRuntimeCompilation().AddMvcOptions(options =>
    {
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    });

    services.AddMvc();

    //Other stuff... 
}

I have tried for many days trying to find either a resolution workaround for this. I can catch the error, but there is no action we can take programmatically that seems to clear the problem (the ITokenAcquisition interface does not offer the option to force an interactive login).

I have found that it is ONLY a problem in a Razor Pages application - a controller-based MVC Web App with almost identical startup code does not exhibit the problem.

I have also found that, by creating a controller-based test MVC Web App and configuring it with the same client id, tenant id etc. as the app we're having problems with, then starting it up (within the Visual Studio development environment) as soon as the main app gets the problem, I can clear the error condition reliably every time. However this is obviously not a viable long-term solution.

I have searched for this problem on every major technical forum and seen a number of similar sorts of issues raised, but none provides a solution to this precise problem.

To replicate:

  1. Create an ASP.NET Core 3.1 Web API.
  2. Create an ASP.NET Core 3.1 Razor Pages Web App that calls the API.
  3. Register both with Azure Active Directory and configure the App to request a token to access the API (as per various MS documents).
  4. Run - if everything is set up correctly the login screen will appear and all will work correctly.
  5. Stop the Web App, wait a couple of minutes and re-start. The error above will now appear.

I have raised a Microsoft support request for it - has anybody else come across this and found a solution for it?

Upvotes: 0

Views: 1094

Answers (1)

user3529977
user3529977

Reputation: 245

I have finally got to the bottom of this, largely thanks to this: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/216#issuecomment-560150172

To summarise - for anyone else having this issue:

  1. On the first invocation of the web app you are not signed in, and so get redirected to the Microsoft Identity Platform login, which logs you in and issues an access token.
  2. The access token is stored in the In-Memory token cache through the callback.
  3. All then works as expected because the token is in the cache.
  4. When you stop, and then re-start the web app within a reasonably short time, it uses the authentication cookies to pick up the still-current login, and so it does not access the Identity Platform and you do NOT get an access token.
  5. When you ask for a token the cache is empty - so it throws the MsalUiRequiredException.

What isn't really made clear in any of the documentation is that this is supposed to happen - and that exception is picked up by the "AuthorizeForScopes" attribute but only if you allow the exception to fall all the way through and don't try to handle it.

The other issue is that in a Razor Pages app the normal AuthorizeForScopes attribute has to go above the model class definition for every page - and if you miss one it may trigger the above problem.

The solution proposed by "jasonshave" in the linked article solves that problem by replacing the attribute with a filter - so it will apply to all pages.

Maybe I'm a bit old-school, but the idea of using an unhandled exception as part of a planned program control flow doesn't sit right with me - at the very least it should be made clear that that's the intention. Anyway - problem now solved.

Upvotes: 2

Related Questions