Search code examples
c#ajaxasp.net-coreasp.net-core-2.0asp.net-identity-2

ASP.NET Core Identity 2.0 - Return 401 status code to ajax function


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.


Solution

  • 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() :

    • I returned the wrong code when the user hadn't the right (Unauthorized instead of Forbid result)
    • When my user wasn't logged, I returned a redirectResult to my Login page instead of a StatusResult. I changed it for an UnauthorizedResult().

    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 !