Click here to Skip to main content
14,265,813 members
Rate this:
Please Sign up or sign in to vote.
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.

What I have tried:

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

Web.config:I added the below code under system.webserver tag
<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>


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??
Posted
Updated 4 days ago
v3

1 solution

Rate this:
Please Sign up or sign in to vote.

Solution 1

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 and Bogdan for their inputs and suggestions. As these suggesstions gave me a hint to resolve the issue.
   
v4
Comments
Member 14558413 2 days ago
   
Where does this angular part go?

Do I add it a ts file?

Thanks


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)

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100