CORS issue when angular and web API(.NET core) is used [SOLVED] [duplicate]

左心房为你撑大大i 提交于 2019-12-08 14:08:35

问题


I have two separate project, one is WebAPI developed in .net Core 2.2 with Windows Authentication and other is Angular. I am stuck in CORS issue. I was able to handle GET request by using withCredentials: true in GET method option as mentioned below, where httpClient is from import { HttpClient } from '@angular/common/http':

return this.httpClient.get(this.getWebApiServiceUrl('/applicationusers/userprofile'), { params: this.httpParams, withCredentials: true }) as Observable<UserProfile>;

But in case of POST, the request is going as OPTION. And every time it is failing with error code 401 UNAUTHORIZED in Network tab of Chrome Developer Tools window. And in Console it is showing the below error

Access to XMLHttpRequest at 'http://localhost:5000/api/xxx/xxxMethod' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

To resolve this issue I made few changes in the following files of Web AP project:

Web.config:I added the below code under system.webserver tag

<system.webServer>
    <security>
        <requestFiltering>
          <verbs>
            <remove verb="OPTIONS" />
            <add verb="OPTIONS" allowed="true" />
          </verbs>
        </requestFiltering>
    </security>
    <httpErrors errorMode="Detailed" />
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" hostingModel="InProcess">
        <environmentVariables>
          <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
          <environmentVariable name="COMPLUS_ForceENC" value="1" />
        </environmentVariables>
    </aspNetCore>
    <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
        <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
        <remove name="OPTIONSVerbHandler" />
        <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
    <httpProtocol>
        <customHeaders>
          <add name="Access-Control-Allow-Origin" value="http://localhost:4200" />
          <add name="Accept" value="application/json, text/plain, */*"/>
        </customHeaders>
    </httpProtocol>
</system.webServer>

PreflightRequestMiddleware.cs: I created this middleware to handle all the incoming request and to bypass the OPTIONS request with OK status

public class PreflightRequestMiddleware
{
  private readonly RequestDelegate Next;
  public PreflightRequestMiddleware(RequestDelegate next)
  {
    Next = next;
  }
  public Task Invoke(HttpContext context)
  {
    return BeginInvoke(context);
  }
  private Task BeginInvoke(HttpContext context)
  {
    context.Response.Headers.Add("Access-Control-Allow-Credentials", new[] { "true" });
    context.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Origin, X-Requested-With, Content-Type, Accept, Athorization, ActualUserOrImpersonatedUserSamAccount, IsImpersonatedUser" });
    context.Response.Headers.Add("Access-Control-Allow-Methods", new[] { "GET, POST, PUT, DELETE, OPTIONS" });
    if (context.Request.Method == HttpMethod.Options.Method)
    {
      context.Response.StatusCode = (int)HttpStatusCode.OK;
      return context.Response.WriteAsync("OK");
    }
    return Next.Invoke(context);
  }
}

public static class PreflightRequestExtensions
{
  public static IApplicationBuilder UsePreflightRequestHandler(this IApplicationBuilder builder)
  {
    return builder.UseMiddleware<PreflightRequestMiddleware>();
  }
}

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
  services.AddCors(o => o.AddPolicy("CorePolicy", builder =>
  {
    builder.AllowAnyMethod();
  }));
  .......
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
  app.UsePreflightRequestHandler();
  .....
  app.UseCors("CorePolicy"); //Tried to put this first line too, but no luck
  .....
}

Then in Angular Project, for POST method call I first create headers:

AbstractReadOnlyService.httpHeaders = new HttpHeaders().set('Content-Type', 'application/json');
AbstractReadOnlyService.httpHeaders = AbstractReadOnlyService.httpHeaders.set('Accept-Language', ['en-US', 'en', 'q=0.9']);
AbstractReadOnlyService.httpHeaders = AbstractReadOnlyService.httpHeaders.set('Accept', ['application/json', 'text/plain', '*/*']);
AbstractReadOnlyService.httpHeaders = AbstractReadOnlyService.httpHeaders.set('Athorization', 'Include');

AbstractReadOnlyService.optionsStatic = {
            headers: AbstractReadOnlyService.httpHeaders,
            params: new HttpParams()
        };

return this.httpClient.post(url, firmDashboardSearchCriteria, AbstractReadOnlyService.optionsStatic).pipe(
            map((response: any) => response as xxxxSearchResult[]),
            catchError(this.handleError)

But, there is one strange thing which I noticed, When I run Fiddler first and then when I run Web API and Angular app all the OPTIONS request is handled in PreflightRequestMiddleware. But when I am running without Fiddler the request is not even reaching to PreflightRequestMiddleware. I have spent 4 days but still has no idea what is wrong. Few people may suggest me to check the header which is received when running Fiddler in Request, but I tried that too with no luck. Does anyone has any clue??

Can any let me know how to pass windows credentials along with the POST request, as currently in the response header I am getting WWW-Authenticate: Negotiate, NTLM which means the windows credentials are not passed to the server.

The suggestions mentioned in linked question doesn't solve my problem. I am still getting the same issue. Please remove the duplicate tag, as it has nothing to do with IIS or setting CORS. I am able to get data with GET request and only POST request is causing the problem. The problem also I have identified, which is that the OPTIONS request is not sending Windows Credentials to API, due to which the POST request is not getting processed.

I hope @Jota, Toledo now you will understand that this is not a duplicate question.

WORKING SOLUTION

Finally, I was able to figure out how to resolve the above Issue. Since I cannot answer my own question here so I am posting the solution here:

Solution: I removed all the above code and started fresh, as mentioned below with files:

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
  services.AddCors(options =>
  {
    options.AddPolicy(
      "CorsPolicy",
      builder => builder.WithOrigins("http://localhost:4200")
      .AllowAnyMethod()
      .AllowAnyHeader()
      .AllowCredentials());
    });
  services.AddAuthentication(IISDefaults.AuthenticationScheme);
  .....
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
  app.UseCors("CorsPolicy");

  app.UsePreflightRequestHandler();
  .......
}

launchSettings.json: I set Anonymous and Windows Authentication to true

{
  "iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:5000",
      "sslPort": 0
    }
  },
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "TPIGO.WebAPI": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:5000"
    }
  }
}

Then, in Angular:

AbstractReadOnlyService.httpHeaders = new HttpHeaders().set('Content-Type', 'application/json');
AbstractReadOnlyService.httpHeaders = AbstractReadOnlyService.httpHeaders.set('Accept', 'application/json');

AbstractReadOnlyService.optionsStatic = {
            headers: AbstractReadOnlyService.httpHeaders,
            params: new HttpParams(),
            withCredentials: true
        };

//For GET
return this.httpClient.get(this.getWebApiServiceUrl('/applicationusers/userprofile'), { params: this.httpParams, withCredentials: true }) as Observable<UserProfile>;

//For POST
return this.httpClient.post(url, firmDashboardSearchCriteria, AbstractReadOnlyService.optionsStatic).pipe(
            map((response: any) => response as xxxxSearchResult[]),
            catchError(this.handleError)

Now, comes the explanation to this solution.

As I mentioned in my problem statement, the GET request was working fine, but the issue was with the POST request.

In case of POST request, the browser was sending OPTIONS request first to authenticate with the server but this request will never reach the Server and that was the reason I was not able to handle the request in PreflightRequestMiddleware.

The OPTIONS request was failing because the API was configured for Windows Authentication and OPTIONS request was not carrying any Authentication with them.

That is the reason I enabled Anonymous Authentication in launchSettings.json.

But, when I enable Anonymous Authentication I started getting 500 Internal Server errors, and investigating it further I came to know that I need to provide Authentication Scheme.

Then I added services.AddAuthentication(IISDefaults.AuthenticationScheme); in Startup.cs files ConfigureServices(IServiceCollection services) method. and everything works like a charm.

Remember to add withCredentials: true with every request you send to the API that needs Authentication.

If anyone has any doubts or confusion feel free to ask here. I am not closing this post so that others can share their doubts here wrt the solution I mentioned.

NOTE: I am still using PreflightRequestMiddleware just to do some additional stuff on Request and Response, but this middleware is not required.

public class PreflightRequestMiddleware
    {
        private readonly RequestDelegate Next;

        public PreflightRequestMiddleware(RequestDelegate next)
        {
            Next = next;
        }

        public Task Invoke(HttpContext context)
        {
            return BeginInvoke(context);
        }

        private Task BeginInvoke(HttpContext context)
        {
            // Do stuff here
            return Next.Invoke(context);
        }
    }

    public static class PreflightRequestExtensions
    {
        public static IApplicationBuilder UsePreflightRequestHandler(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<PreflightRequestMiddleware>();
        }
    }

Thanks to Manoj, Bogdan and Jota for their inputs and suggestions. As these suggesstions gave me a hint to resolve the issue.


回答1:


Take a look at using a proxy.conf.json file in your Angular application. This will enable you to divert certain URL's to a backend server. With this you'll no longer receive the infamous CORS error.

In the proxy.conf.json file, you can pass your authentication/authorization in the headers.

{
"/api/*": {
    "target": "http://thisroute.com/",
    "secure": false,
    "logLevel": "debug",
    "changeOrigin": true,
    "headers": { 
      "Content-Type": "application/json",
      "Authorization": "your auth here"
    }
  }
}

Here's the official angular documentation: https://angular.io/guide/build#proxying-to-a-backend-server

Here's a video I found useful: https://www.youtube.com/watch?v=OjmZPPKaj6A



来源:https://stackoverflow.com/questions/57060019/cors-issue-when-angular-and-web-api-net-core-is-used-solved

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!