Search code examples
c#asp.net-coreasp.net-core-mvc

ASP.NET Core - error 404.15 after adding authorization services


I am working on an ASP.NET Core MVC project and recently added custom authorization services to my Program.cs file:

After doing this, I encountered the following error when accessing my application:

URL

https://localhost:44340/Identity/Account/Login?ReturnUrl=%2FIdentity%2FAccount%2FLogin%3FReturnUrl%3D%252FIdentity%252FAccount%252FLogin%253FReturnUrl%253D%25252FIdentity%25252FAccount%25252FLogin%25253FReturnUrl%25253D%2525252FIdentity%2525252FAccount%2525252FLogin%2525253FRet

HTTP Error 404.15 - Not Found
The request filtering module is configured to deny a request where the query string is too long.

Program.cs file:

builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
builder.Services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
builder.Services.Configure<SecurityStampValidatorOptions>(options =>
{
    options.ValidationInterval = TimeSpan.Zero;
});
using Microsoft.AspNetCore.Authorization;

namespace UserManagement.Filters
{
    public class PermissionRequirement : IAuthorizationRequirement
    {
        public string Permission { get; private set; }

        public PermissionRequirement(string permission)
        {
            Permission = permission;
        }
    }
}
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;

namespace UserManagement.Filters
{
    public class PermissionPolicyProvider : IAuthorizationPolicyProvider
    {
        public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }

        public PermissionPolicyProvider(IOptions<AuthorizationOptions> options)
        {
            FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
        }

        public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
        {
            return FallbackPolicyProvider.GetDefaultPolicyAsync();
        }

        public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
        {
            return FallbackPolicyProvider.GetDefaultPolicyAsync();
        }

        public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
        {
            if (policyName.StartsWith("Permission", StringComparison.OrdinalIgnoreCase))
            {
                var policy = new AuthorizationPolicyBuilder();
                policy.AddRequirements(new PermissionRequirement(policyName));
                return Task.FromResult(policy.Build());
            }

            return FallbackPolicyProvider.GetPolicyAsync(policyName);
        }
    }
}
using Microsoft.AspNetCore.Authorization;
using System.Linq;
using System.Threading.Tasks;

namespace UserManagement.Filters
{
    public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
    {
        public PermissionAuthorizationHandler()
        {
        }

        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
        {
            if (context.User == null)
                return;

            var canAccess = context.User.Claims.Any(c => c.Type == "Permission" && c.Value == requirement.Permission && c.Issuer == "LOCAL AUTHORITY");

            if (canAccess)
            {
                context.Succeed(requirement);
                return;
            }
        }
    }
}

Login method

public async Task OnGetAsync(string returnUrl = null)
{
    if (!string.IsNullOrEmpty(ErrorMessage))
    {
        ModelState.AddModelError(string.Empty, ErrorMessage);
    }

    returnUrl ??= Url.Content("~/");

    // Clear the existing external cookie to ensure a clean login process
    await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();

    ReturnUrl = returnUrl;
}

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl ??= Url.Content("~/");

    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
    var username = new EmailAddressAttribute().IsValid(Input.Email) ? new MailAddress(Input.Email).User : Input.Email;

    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(username, Input.Password, Input.RememberMe, lockoutOnFailure: false);

        if (result.Succeeded)
        {
            _logger.LogInformation("User logged in.");
            return LocalRedirect(returnUrl);
        }

        if (result.RequiresTwoFactor)
        {
            return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
        }

        if (result.IsLockedOut)
        {
            _logger.LogWarning("User account locked out.");
            return RedirectToPage("./Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return Page();
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

Solution

  • https://localhost:44340/Identity/Account/Login?ReturnUrl=%2FIdentity%2FAccount%2FLogin%3FReturnUrl%3D%252FIdentity%252FAccount%252FLogin%253FReturnUrl%253D%25252FIdentity%25252FAccount%25252FLogin%25253FReturnUrl%25253D%2525252FIdentity%2525252FAccount%2525252FLogin%2525253FRet

    For this url, it seems Identity/Account/Login action also requires login, which causes an infinite redirect loop. Add AllowAnonymousAttribute on this PageModel.

    [Microsoft.AspNetCore.Authorization.AllowAnonymous]
    public class LoginModel : PageModel
    

    If you add the attribute but still get such error, be sure modify IIS configuration by enabling Anonymous Authentication and disabling Windows Authentication.

    Also check your web.config file does not have any configuration that conflicts with the IIS settings. :

    <system.webServer>
      <security>
        <authentication>
          <anonymousAuthentication enabled="true" />
          <windowsAuthentication enabled="false" />
        </authentication>
      </security>
    </system.webServer>