question

AK-7992 avatar image
1 Vote"
AK-7992 asked ZhiLv-MSFT commented

Authorization attributes not working when upgrading from MVC to Endpoint routing

I'm trying to upgrade my project from .UseMVC (asp.net core 2.2 compat style) to .UseEndpoint Routing and I'm getting re-directed to my suthentication failed page for all my requests. It has to do with the Claims - If I remove the role part of [Authorize(Roles = "Admin")] to simply [Authorize] then it works. It seems that it isn't picking up the claims that are assigned to the user.

It seems to be a very similar issue as https://stackoverflow.com/q/60388730/5683904

Everything worked fine in 2.2, but after migrating to 3.1 and enabling Endpoint Routing, this controller began to refuse requests to any endpoint when [Authorize] attribute is present, When I remove [Authorize] and look at User.Claims, I can see that it does have the required claims (i.e. scope: my-scope, role: MyRole). This happens only if Endpoint Routing is enabled, in case of using UseMvc everything works properly. What's wrong with Authorization in Endpoint Routing mode?

except that I don't even have any custom policies.


Excerpt from Startup.cs

  public void ConfigureServices(IServiceCollection services)
  {
   services.AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddAzureAd(options =>
    {
       Configuration.Bind("AzureAd", options);
       AzureAdOptions.Settings = options;
     })
    .AddAzureAdBearer(options =>
    {
        Configuration.Bind("AzureAd", options);
        AzureAdOptions.Settings = options;
    })
    .AddCookie(options =>
    {
        options.ForwardDefaultSelector = context =>
        {
            //Use Bearer authentication if request contains JWT Bearer token
            if (context.Request.Headers.TryGetValue("Authorization", out var value) &&
                value.Any(v => v.StartsWith("bearer", StringComparison.OrdinalIgnoreCase)))
            {
                return "Bearer";
            }
    
            return null;
        };
    
        options.Events.OnRedirectToLogin = context =>
        {
            if (context.Request.Headers["Content-Type"].Any(a => a.Contains("application/json")))
            {
                context.HttpContext.Response.StatusCode = 401;
            } 
    
            return Task.CompletedTask;
        };
    });
    ...
    var mvcBuilder = services.AddMvc()
       .AddSessionStateTempDataProvider()
       .AddNewtonsoftJson(options =>
       {
           options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
           options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
       }); 
    
    if (_env.IsDevelopment())
    {
        mvcBuilder.AddRazorRuntimeCompilation();
    }
    
    services.AddDistributedMemoryCache();
    services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromHours(12); 
        options.Cookie.HttpOnly = true;
    }); 
    
    //Add distributed cache service backed by Sql cache
    services.AddDistributedSqlServerCache(o =>
    {
         o.ConnectionString = sqlConnectionString;
        o.SchemaName = "dbo";
        o.TableName = "SessionCache";
    });
     
    services.ConfigureApplicationCookie(options =>
    {
        options.ExpireTimeSpan =
            TimeSpan.FromHours(12); 
        options.SlidingExpiration = true;
    });
    
    ... (rest of Configure services)
  }
 public void Configure(
  ...
 app.UseSession();
    
 app.UseRouting();
    
 app.UseAuthentication();
 app.UseAuthorization();
 app.UseResponseCompression();
    
 //Add the users Roles as claims to his identity so that it is picked up for authentication purposes
 app.Use((context, next) =>
 {
     var userId = context.User.Identity.Name;
     if (userId == null)
     {
         return next();
     }
    
     ...
        
     var roles = resourceDataAccess.GetRolesForUser(userId);
     if (roles != null)
     {
         var claims = roles.Select(role => new Claim(ClaimTypes.Role, role.RoleEnum.ToString())).ToList();
    
         var appIdentity = new ClaimsIdentity(claims);
         context.User.AddIdentity(appIdentity);
     }
    
     return next();
 });
 app.UseEndpoints(endpoints =>
 {
     endpoints.MapHub<AppHub>("api/apphub");
     endpoints.MapControllerRoute("default", "api/{controller=Account}/{action=SignIn}/{id?}");
     endpoints.MapControllerRoute("catch-all", "api/{*url}",
             new {controller = "Utility", action = "NotFoundPage"});
 });



dotnet-aspnet-core-mvc
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Can you share the related about the authentication configuration in ConfigureServices() in your Startup.cs file?

0 Votes 0 ·

I added the requested code to the question

0 Votes 0 ·

1 Answer

AK-7992 avatar image
1 Vote"
AK-7992 answered ZhiLv-MSFT commented

It turns out since we were using app.Use() middleware to fill in the user's roles from the DB, it needed to be called before UseAuthorisation so that the roles were loaded before authorisation was performed.

  app.UseSession();
        
  app.UseRouting();
        
  app.UseAuthentication();
  //Add the users Roles as claims to his identity so that it is picked up for authentication purposes
  app.Use((context, next) =>
  {
    ...
  }
  //Setup the authorisation middleware to run only after we have loaded the users roles.
  app.UseAuthorization();
  app.UseResponseCompression();
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Glad to hear that you have solved the problem, congratulation!

0 Votes 0 ·