2

I have a regular application using cookie based authentication. This is how it's configured:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication("Login")
            .AddCookie("Login", c => {
                c.ClaimsIssuer = "Myself";
                c.LoginPath = new PathString("/Home/Login");
                c.AccessDeniedPath = new PathString("/Home/Denied");
            }); 
}

This works for my regular actions:

[Authorize]
public IActionResult Users()
{
    return View();
}       

But doesn't work well for my ajax requests:

[Authorize, HttpPost("Api/UpdateUserInfo"), ValidateAntiForgeryToken, Produces("application/json")]
public IActionResult UpdateUserInfo([FromBody] Request<User> request)
{
    Response<User> response = request.DoWhatYouNeed();

    return Json(response);
}

The problem is that when the session expires, the MVC engine will redirect the action to the login page, and my ajax call will receive that. I'd like it to return the status code of 401 so I can redirect the user back to the login page when it's an ajax request. I tried writing a policy, but I can't figure how to unset or make it ignore the default redirect to login page from the authentication service.

public class AuthorizeAjax : AuthorizationHandler<AuthorizeAjax>, IAuthorizationRequirement
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorizeAjax requirement)
    {
        if (context.User.Identity.IsAuthenticated)
        {
            context.Succeed(requirement);
        }
        else
        {   
            context.Fail();
            if (context.Resource is AuthorizationFilterContext redirectContext)
            {
                // - This is null already, and the redirect to login will still happen after this.
                redirectContext.Result = null;
            }
        }

        return Task.CompletedTask;
    }
}

How can I do this?

Edit: After a lot of googling, I found this new way of handling it in version 2.0:

services.AddAuthentication("Login")
        .AddCookie("Login", c => {
                   c.ClaimsIssuer = "Myself";
                   c.LoginPath = new PathString("/Home/Login");
                   c.Events.OnRedirectToLogin = (context) =>
                   {
                       // - Or a better way to detect it's an ajax request
                       if (context.Request.Headers["Content-Type"] == "application/json")
                       {
                           context.HttpContext.Response.StatusCode = 401;
                       }
                       else
                       {
                           context.Response.Redirect(context.RedirectUri);
                       }

                       return Task.CompletedTask;
                   };                       
        });

And it works for now!

Tetsuya Yamamoto
  • 24,297
  • 8
  • 39
  • 61
Danicco
  • 1,573
  • 2
  • 23
  • 49
  • what is happening when you call ajax request ? are you getting 401 or you are getting some other exception ? – Manoj Choudhari Mar 25 '19 at 19:29
  • Check [this](https://stackoverflow.com/a/11085769/1955268) answer. It is not for core, but I think it should work. – prinkpan Mar 25 '19 at 19:37
  • @ManojChoudhari I get no errors, I get the HMTL code of the login page instead. – Danicco Mar 25 '19 at 19:40
  • I had the same issue for AzureAD and solved it based on your answer - see https://github.com/aspnet/AspNetCore/issues/7348 - I modified AzureAdAuthenticationBuilderExtensions.cs 2 differences: 1) change the OnRedirectToIdentityProvider and 2) You need to call context.HandleResponse(); in order for the statuscode to be propogated to the user. – akraines Apr 28 '19 at 13:08

1 Answers1

0

What you need can be achieved by extending AuthorizeAttribute class.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
             filterContext.HttpContext.Response.StatusCode = 401;
             filterContext.Result = new JsonResult
             {
                 Data = new { Success = false, Data = "Unauthorized" },
                 ContentEncoding = System.Text.Encoding.UTF8,
                 ContentType = "application/json",
                 JsonRequestBehavior = JsonRequestBehavior.AllowGet
             };
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}

You can then specify this attribute on Ajax methods.

Hope this helps.

Reference: http://benedict-chan.github.io/blog/2014/02/11/asp-dot-net-mvc-how-to-handle-unauthorized-response-in-json-for-your-api/

Manoj Choudhari
  • 5,277
  • 2
  • 26
  • 37
  • I just tried this, from your other comment, but it doesn't work unfortunately. It seems .Net Core's MVC login service overrides anything that comes before, it'll turn all 401 (and some other responses) to it's configured or default value. I tested using other response codes, but the login service seems to redirect everything else to it's `DeniedPath`, even when it's not set. It seems it's made specifically to be anti-ajax in all ways... – Danicco Mar 25 '19 at 20:01
  • Did you apply custom authorize attribute to action ? At that time was there the authorize attribute on controller? If yes in that cas can you apply custom authorize attribute at the controller leveland check again. – Manoj Choudhari Mar 26 '19 at 05:34
  • See my comment above: https://stackoverflow.com/questions/55344665/handling-session-timeout-with-ajax-in-net-core-mvc#comment98438012_55344665 I needed to call context.HandleResponse(); to solve your issue in my case. – akraines Apr 28 '19 at 13:09