Authorization attributes not working when upgrading from MVC to Endpoint routing

AK 11 Reputation points
2020-11-26T16:34:18.03+00:00

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"});
});
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,156 questions
{count} vote

1 answer

Sort by: Most helpful
  1. AK 11 Reputation points
    2020-11-29T16:00:37.167+00:00

    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 person found this answer helpful.