crissb3
crissb3

Reputation: 89

fail: Microsoft.Identity.Web.TokenAcquisition[0] Exception type: Microsoft.Identity.Client.MsalUiRequiredException

I am getting this error every time I build my application:

fail: Microsoft.Identity.Web.TokenAcquisition[0]
      False MSAL 4.49.1.0 MSAL.NetCore .NET 7.0.0 Microsoft Windows 10.0.19043 Exception type: Microsoft.Identity.Client.MsalUiRequiredException
      , ErrorCode: user_null
      HTTP StatusCode 0
      CorrelationId

         at Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
         at Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
         at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken)

I am building upon this project: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/2-WebApp-graph-user/2-1-Call-MSGraph

Other than the error, the app is working fine except for another error previously mentioned (token not automatically refreshing after about an hour) here: Microsoft graph token refresh azure app service. However, this problem seemed to be a bug with .net 7.

My Startup class:

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
            
        }

        public IConfiguration Configuration { get; }
        
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Get the scopes from the configuration (appsettings.json)
            var initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
            
            // Add sign-in with Microsoft
            services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))

                // Add the possibility of acquiring a token to call a protected web API
                .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)

                // Enables controllers and pages to get GraphServiceClient by dependency injection
                // And use an in memory token cache
                .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
                .AddDistributedTokenCaches();



            // Register AadService and PbiEmbedService for dependency injection
            services.AddScoped(typeof(AadService))
                    .AddScoped(typeof(PbiEmbedService))
                    .AddScoped(typeof(PowerBiServiceApi));

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

            // Enables a UI and controller for sign in and sign out.
            services.AddRazorPages()
                .AddMicrosoftIdentityUI();
            
            // Session/cookie variables etc

            services.AddDistributedMemoryCache();
            services.AddSession();
            
            
            // Loading appsettings.json in C# Model classes
            services.Configure<AzureAd>(Configuration.GetSection("AzureAd"))
                    .Configure<PowerBI>(Configuration.GetSection("PowerBI"));
            
            // Add the UI support to handle claims challenges
            services.AddServerSideBlazor()
                .AddMicrosoftIdentityConsentHandler();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseSession();
            
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });
        }
    }

Controller class:

[Authorize]
    public class HomeController : Controller
    {
        public IConfiguration Configuration { get; }
        private readonly GraphServiceClient _graphServiceClient;

        public HomeController(IConfiguration configuration,
                                GraphServiceClient graphServiceClient)
        {
            Configuration = configuration;
            _graphServiceClient = graphServiceClient;
        }
        [AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
        public Task<IActionResult> Index()
        {
            var graphUser = _graphServiceClient.Me.Request().GetAsync();
            var datasource = Configuration["AzureDB:Datasource"];
            var username = Configuration["AzureDB:Username"];
            var password = Configuration["AzureDB:Password"];
            var database = Configuration["AzureDB:Database"];
            var user = new Userclass(graphUser.Result, datasource, username, password, database);
            Console.WriteLine(user.ProjectName);
            if (!user.ProjectName.IsNullOrEmpty())
            {
                HttpContext.Session.SetString("MyProject", user.ProjectName);
            }else{
                
                /*HttpContext.Session.SetString("MyProject", "No Project");*/
            }
            
            /*var user = _graphServiceClient.Me.Request().GetAsync();
            Console.WriteLine(user.Result.DisplayName);*/
            return Task.FromResult<IActionResult>(View());
        }
        [AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
        public async Task<IActionResult> Profile()
        {

            var user = await _graphServiceClient.Me.Request().GetAsync();
            

            return View(user);
        }

Upvotes: 1

Views: 5382

Answers (1)

Tiny Wang
Tiny Wang

Reputation: 15961

The error message indicating the issue happened in TokenAcquisition, but the sample link you shared doesn't have the method which used TokenAcquisition. So I'm not sure where and how the issue happened. I noticed that you used .net 7, so I also created a new .net 7 MVC application then follow the sample link to test and it worked. Here's what I did in my side.

First, adding _LoginPartial.cshtml in Shared folder, the content is completely copied from the sample, then adding this partial view into _layout.cshtml. Then adding configuration in appsettings.json. I set https://localhost/signin-oidc in Azure portal web app platform.

"AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "tenant_id",
    "TenantId": "tenant_id",
    "ClientId": "azure_ad_app_id",
    "ClientSecret": "azure_ad_app_client_secret",
    //"ClientCertificates": [
    //],
    // the following is required to handle Continuous Access Evaluation challenges
    //"ClientCapabilities": [ "cp1" ],
    "CallbackPath": "/signin-oidc"
  },
  "DownstreamApi": {
    "BaseUrl": "https://graph.microsoft.com/v1.0",
    "Scopes": "user.read"
  }

Then modifying Program.cs, also adding app.UseAuthentication();.

using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(builder.Configuration)
                .EnableTokenAcquisitionToCallDownstreamApi(builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '))
                .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
                .AddInMemoryTokenCaches();

// Add services to the container.
builder.Services.AddControllersWithViews(options =>
{
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();

Then in my Controller, adding [Authorize] attribute for the controller, and inject graphsdk,

private readonly GraphServiceClient _graphServiceClient;

public HomeController(GraphServiceClient graphServiceClient)
{
    _graphServiceClient = graphServiceClient;
}

public async Task<IActionResult> IndexAsync()
{
    var user = await _graphServiceClient.Me.Request().GetAsync();
    return View();
}

Packages I installed:

<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.2" />
    <PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="1.25.10" />
    <PackageReference Include="Microsoft.Identity.Web.UI" Version="1.25.10" />

By the way, I also test the TokenAcquisition in my Controller. It became like below. It still worked.

private readonly GraphServiceClient _graphServiceClient;
private readonly ITokenAcquisition _tokenAcquisition;

public HomeController(GraphServiceClient graphServiceClient, ITokenAcquisition tokenAcquisition)
{
    _graphServiceClient = graphServiceClient;
    _tokenAcquisition = tokenAcquisition;
}

[AuthorizeForScopes(ScopeKeySection = "user.read")]
public async Task<IActionResult> IndexAsync()
{
    var user = await _graphServiceClient.Me.Request().GetAsync();
    string[] scopes = new string[] { "user.read" };
    string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);
    return View();
}

enter image description here

But if I removed [AuthorizeForScopes(ScopeKeySection = "user.read")] for the action method, I will meet error like this, this is due to the sign in operation happened after the Index action method without the attribute.

enter image description here

Upvotes: 2

Related Questions