ASP.NET Core 6 MVC : cors error on HttpPost only with authorization policy

113 Views Asked by At

I have added my ASP.NET Core 6 MVC application to Azure "App registrations" and I'm able to login using the Microsoft WebUI and perform GET requests to obtain e.g. my views.

The application is hosted on IIS 8.5.

I've also defined a few App roles in my Azure App registration panel. When I click on a button that sends a POST request though, a preflight request is sent and I see the following error:

Access to fetch at 'https://login.microsoftonline.com/[omitted]/oauth2/v2.0/authorize?client_id=[omitted]&redirect_uri=https%3A%2F%2F[omitted]%2Fsignin-oidc&response_type=id_token&scope=openid%20profile&response_mode=form_post&[omitted] from origin '[omitted]' 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.

I have tried:

  1. Editing web.config to exclude handling OPTIONS requests from the default ASP.NET Core handler and removing handlers like ExtensionlessUrlHandler-Integrated-4.0
  2. Adding UseCors to Program.cs
  3. Handling all OPTIONS requests in Program.cs so that they receive a 200 response
  4. Checked the Web URIs I've added under Azure App Registrations (which should work, as I'm able to login...)
  5. Something else I don't recall (but from StackOverflow posts related to Cors, App registrations etc.)

But I've removed many of the above as they don't seem to work.

I don't understand why GET requests to my MVC controllers have no problem, while POST requests fail (because a preflight request is sent and hates me, while no preflight request is sent before a GET request).

Also, no SPA app is involved here, just plain MVC with views that reference ordinary vanilla js files (containing some fetch calls to POST).

Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton... // adding my services like db repository etc.

JwtSecurityTokenHandler.DefaultMapInboundClaims = false;

builder.Services
    .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Admin", policy => policy.RequireClaim("roles", "Admin"));
    options.AddPolicy("Purchaser", policy => policy.RequireClaim("roles", "Purchaser", "Admin"));
    options.FallbackPolicy = options.DefaultPolicy;
});

builder.Services.AddControllersWithViews(options =>
{
    var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
    options.Filters.Add(new AuthorizeFilter(policy));
})
.AddMicrosoftIdentityUI();

var app = builder.Build();

//...

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

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

// app.MapAreaControllerRoute and stuff

app.Run();

web.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <location path="." inheritInChildApplications="false">
        <system.webServer>
            <handlers>
                <add name="aspNetCore" path="*" verb="GET,POST,PUT,PATCH,DELETE" modules="AspNetCoreModuleV2" resourceType="Unspecified"/>
                <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit"/>
                <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit"/>
                <remove name="ExtensionlessUrlHandler-Integrated-4.0"/>
                <remove name="OPTIONSVerbHandler"/>
                <remove name="WebDav"/>
                <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0"/>
                <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0"/>
                <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0"/>
            </handlers>
            <aspNetCore processPath="dotnet" arguments=".\TaskBridgePortal.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess"/>
        </system.webServer>
    </location>
    <system.webServer>
        <httpProtocol>
            <customHeaders>
                <!--add name="Access-Control-Allow-Methods" value="*" />
  <add name="Access-Control-Allow-Origin" value="*" />
  <add name="Access-Control-Allow-Headers" value="*" /-->
            </customHeaders>
        </httpProtocol>
    </system.webServer>
</configuration>

A sample controller:

[Authorize(Policy = "Orders")]
[Area/*..*/]
public class ReportController : Controller
{
    public ReportController(/* ... */)
    {
        // ...
    }

    [HttpPost] // fails
    public async Task<IActionResult> DownloadReportAsync([FromBody] DownloadReportRequest request)
    {
        // ...
    }

    public async Task<IActionResult> ReportAsync(bool returnView = true) // doesn't fail
    {
        //... 
        return View(/* ... */);
    }
}

What am I missing?

1

There are 1 best solutions below

0
Pavlodar On

You should register your CORS with the necessary URL ("*" for a lot of URLs)

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy  =>
        {
            policy.WithOrigins("http://example.com");
        });
});

app.UseCors(MyAllowSpecificOrigins);

Also, you can register CORS for static files

builder.Services.Configure<StaticFileOptions>(options =>
{
    options.OnPrepareResponse = ctx =>
    {
        ctx.Context.Response.Headers.Append("Access-Control-Allow-Origin", "http://example.com");
    };
});

More information you can find below:

https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-6.0