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.
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?}");
});
}
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