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();
}
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>