-1

I'm implementing authentication and authorization in a ASP.NET MVC Core 3.1 application. I have [Authorize] with specified roles on my actions. Some of those actions get called by AJAX and they work fine as long as the user remains authenticated. Even if the user does not have the role to access an action called by AJAX, I can just catch http status code 403 in my global AJAX error handler and redirect them to Not Authorized page:

$(document).ajaxError(function (event, xhr, ajaxSettings, thrownError) {
    if (xhr.status == 403) {
        window.location = "/UserAccess/NotAuthorized";
    }
});

However, if I sign-out out from the application and then try to run an action that is called through AJAX, I get the following error in console:

Access to XMLHttpRequest at 'https://login.microsoftonline.com/.......' (redirected from 'https://localhost:44319/Mfgrs/GetEditMfgrPartialView') from origin 'https://localhost:44319' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

1

Nothing occurs on the front-end and I don't get redirected to the page to reauthenticate me, like I would be if the action was requested normally (without using AJAX, just by regular form submission). I don't even get a status 401 so I could at least redirect the user to the sign-in page. The status is just 0 so it doesn't tell me what happened.

I know there are a ton of questions about this CORS policy issue with AJAX, but I could not get anything to work. I tried the different solutions listed in Microsoft docs, and a few from stackoverflow. I ended up finding this article that says:

Some CORS issues can't be resolved, such as when your app redirects to login.microsoftonline.com to authenticate, and the access token expires. The CORS call then fails. A workaround for this scenario is to extend the lifetime of the access token, to prevent it from expiring during a user’s session.

What I DON'T want to do is implement a custom authorization attribute because I don't trust that it will be as secure. I can't even inherit from AuthorizeAttribute anyway without CORS issue presenting itself. Also, according to this thread, Microsoft recommends never creating your own solution.

My question is, how can I get the [Authorize] attribute to work with AJAX calls? What is the proper way to handle this? Is it even possible without a custom attribute?

A simple action I call through AJAX:

[Authorize(Roles = "ADMIN, MANAGER")]
[HttpGet]
public IActionResult GetAddMfgrPartialView()
{
    return PartialView("_AddMfgrPartial");
}

Startup.cs Configure method:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseDeveloperExceptionPage();

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication(); // who are you?            
    app.UseAuthorization(); // are you allowed?

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=UserAccess}/{action=Index}/{id?}");
    });
}

Lukas
  • 549
  • 1
  • 4
  • 16
  • 2
    I'm not sure that your problem is exactly CORS since you are able to make AJAX calls when you are signed in. More likely an exception is thrown somewhere in the MVC pipeline when an anonymous request is made which results in this CORS message at the browser due to lack of exception handling. Perhaps you should try to add some exception middleware https://docs.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-5.0 and investigate the issue starting from there. – Anton Kovachev Apr 07 '21 at 17:03
  • Can you post your `Startups.Configure` method (your middleware pipeline)? – Pieterjan Apr 07 '21 at 17:28
  • Thanks for commenting. I actually do have `app.UseDeveloperExceptionPage();` middleware added and it does not handle the exception as it normally would. Also, I think the reason why I can make AJAX calls while I'm signed in is because they're all directed the app's website. When I sign out, the `[Authorize]` attribute attempts to redirect to `https://login.microsoftonline.com/` in an attempt to reauthenticate. – Lukas Apr 07 '21 at 17:29
  • @Pieterjan Please see my edited post. – Lukas Apr 07 '21 at 17:35
  • Just a guess... Can you modify the order of the following middleware and try again? UseAuthentication, UseRouting, UseAuthorization, UseEndpoints – Pieterjan Apr 07 '21 at 17:38
  • @Pieterjan I reordered them like you suggested but the problem still exists. – Lukas Apr 07 '21 at 18:42

1 Answers1

-1

Time constraints required me to come up with some sort of solution where I could use the [Authorize] attribute on actions called by AJAX. The solution was to modify AJAX global error handler to the following:

$(document).ajaxError(function (event, xhr, ajaxSettings, thrownError) {
    //403 code returned from [Authorize] filter when user is authenticated but does not have permissions
    if (xhr.status == 403) {
        window.location = "/UserAccess/NotAuthorized";
    } else if (xhr.status == 0) { //else, we assume status 0 probably means user is unauthenticated and we have the CORS policy issue: https://stackoverflow.com/q/66990101/12300287
        window.location = "/Error/Unknown"; //redirect user the Unknown error page, suggesting them to sign-in.
    }
});

If the http status code comes back as 0, I redirect the user to an error page saying an error happened and suggesting for them to sign-in. It is only a suggestion because status code 0 could happen for a number of other reasons.

I've also found some useful SO posts that may help those who want a better solution: https://stackoverflow.com/a/64341476/12300287

https://stackoverflow.com/a/65494627/12300287

https://stackoverflow.com/a/37812311/12300287

Lukas
  • 549
  • 1
  • 4
  • 16