I have a ASP.NET Core Application where I use Identity 2.0. I'm trying to return a 401 status code when a user isn't logged in (or 403 when he isn't authorize) but the different functions returns only a 302 code...
I setted up for my application needs :
A personalized [Authorize] method
[HttpPost]
[RequiresPermission("Projects.Creation, Projects.Modification")]
public IActionResult SaveProject(Project prj)
{
(...)
{
My RequiresPermission is a class which inherit of TypeFilterAttribute and have a function which seems to be useful to set the different returned results :
public async Task OnResourceExecutionAsync(ResourceExecutingContext context,
ResourceExecutionDelegate next)
{
var principal = new CustomPrincipal(_PermissionProvider, context.HttpContext.User.Identity);
bool isInOneOfThisRole = false;
foreach (var item in _requiredPermissions.RequiredPermissions)
{
if (principal.IsInRole(item))
{
isInOneOfThisRole = true;
}
}
if (isInOneOfThisRole == false)
{
if (principal.Identity.IsAuthenticated)
{
context.Result = new UnauthorizedResult();
}
else
{
context.Result = new RedirectResult("~/Connexion/Login");
}
await context.Result.ExecuteResultAsync(context);
}
else
{
await next();
}
}
Different things in my startup method
services.AddIdentity<Utilisateur, Profil>().AddUserManager<CustomUserManager<Utilisateur>>().AddRoleManager<CustomRoleManager>().AddDefaultTokenProviders();
services.AddTransient<IUserStore<Utilisateur>, UserStore>();
services.AddTransient<IRoleStore<Profil>, ProfileStore>();
services.AddTransient<IPermissionProvider, PermissionProvider>();
services.Configure<IdentityOptions>(options =>
{
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
//options.SecurityStampValidationInterval = TimeSpan.FromHours(2);
});
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
// If the LoginPath isn't set, ASP.NET Core defaults the path to /Account/Login.
options.LoginPath = new PathString("/Connexion/Login");
options.LogoutPath = new PathString("/Connexion/SignedOut");
// If the AccessDeniedPath isn't set, ASP.NET Core defaults the path to /Account/AccessDenied.
options.AccessDeniedPath = new PathString("/Connexion/AccessDenied");
options.SlidingExpiration = true;
});
I've tried a ton of suggestions but nothing seems to work like :
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
This solution is not a good solution for my because it redirect ajax AND non-ajax methods which broke my other redirection...
I also tried to override the AuthorizeAttribute class with a class I found on the web. This class seems to be what I want but it doesn't work to ASP.Net Core...
using Microsoft.AspNetCore.Authorization;
using System.Net;
public class ApplicationAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var httpContext = filterContext.HttpContext;
var request = httpContext.Request;
var response = httpContext.Response;
var user = httpContext.User;
if (request.IsAjaxRequest())
{
if (user.Identity.IsAuthenticated == false)
response.StatusCode = (int)HttpStatusCode.Unauthorized;
else
response.StatusCode = (int)HttpStatusCode.Forbidden;
response.SuppressFormsAuthenticationRedirect = true;
response.End();
}
base.HandleUnauthorizedRequest(filterContext);
}
}
Have you an idea to solve my problem ? Or maybe there is something I do wrong.
Thanks to Brad, I found the solution and it was so stupid...
I don't need the ApplicationAuthorizeAttribute
class as I already have my RequiresPermissionAttribute
class.
The problem was just in my function OnResourceExecutionAsync() :
Changed part of my function :
if (isInOneOfThisRole == false)
{
if (principal.Identity.IsAuthenticated)
{
context.Result = new ForbidResult();
}
else
{
context.Result = new UnauthorizedResult();
}
await context.Result.ExecuteResultAsync(context);
}
else
{
await next();
}
And I already have a function in my ajax to catch the different status code :
$(document).ajaxError(function (xhr, props) {
if (props.status == 403) {
window.location = window.location.protocol + '//' + window.location.host + "/Connexion/AccessDenied";
} else if (props.status == 401) {
window.location = window.location.protocol + '//' + window.location.host + "/Connexion/Login";
}
});
And everything works nicely !