.Net 3.1 API & Angular 9 CORS with NGINX Proxy

Viewed 2004

I have been stuck with an issue exchanging data from an Angular 9 UI web app. I know there plenty of topics on the area, but I cannot make it work, I will share my code with you to see if I can get there. I cannot seem to find where the root-cause is, if its .Net API configuration error, NGINX, or the Angular app. This is my first "real" solution, so I am still learning my way around all the components.

I have a .Net 3.1 API one controller, exposing MySQL Db data. The Angular app is behind an NGIX Proxy server deployment.

GET, PUT, POST, DELETE requests work from Postman app or from navigating to the controller address. Also, the GET request works in the Angular app. However, PUT, POST request does not work.

I have tried without success to modify the .NET API Startup configuration adding the CORS Nugget package from Microsoft following their tutorial. After doing this the GET stops working too. Error in Angular App when performing a PUT / POST request:

Access to XMLHttpRequest at 'http://192.168.10.40/api/AlertsConfig/' from origin 'http://localhost:4200' 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.

What I tried:

public void ConfigureServices(IServiceCollection services)
    {
        // ...

        // Add CORS policy
        services.AddCors(options =>
        {
            options.AddPolicy("test",
            builder =>
            {
                // Not a permanent solution, but just trying to isolate the problem
                builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
            });
        });

        services.AddControllers();
    }
 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();

        // Use the CORS policy
        app.UseCors("test");

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }

My .Net Controller - For making it simple I am just interested in PUT

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BeckerSmartFan.Data;
using BeckerSmartFan.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace BeckerSmartFan.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    [AllowAnonymous]
    public class AlertsConfigController : ControllerBase
    {
        private readonly ConnectionStrings con;
        private readonly Data.MySqlConnector mySqlConnector;

        public AlertsConfigController(ConnectionStrings c)
        {
            con = c;
            mySqlConnector = new Data.MySqlConnector();
        }

        // GET: api/AlertsConfig
        [HttpGet]
        public async Task<IActionResult> Get([FromQuery]  IEnumerable<AlertsConfig> alertsConfigs, [FromQuery] string type)
        {
            return await Task.Run(() =>
            {
                var listAlertConfigs = mySqlConnector.GetAllAlertsConfigs(con, type);
                return listAlertConfigs != null ? (IActionResult)Ok(listAlertConfigs) : NotFound();
            });
        }  

        // PUT: api/AlertsConfig/
        [HttpPut]
        public async Task<IActionResult> Put([FromBody] AlertsConfig alertConfig)
        {
            return await Task.Run(() =>
            {
                var result = mySqlConnector.UpdateAlertConfig(con, alertConfig);
                return result > 0 ? (IActionResult)Ok(alertConfig) : BadRequest();
            });
        }    

    }
}

My Angular Http Service:

import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { catchError, map, timeout } from 'rxjs/operators';   
import {  throwError } from 'rxjs';   
import { NotFoundError } from '../common/not-found-error';
import { FanDataTag } from './FanDataTag';

const QueryTimeOut : number = 5000;

export class ApiDataService {
  constructor(private url: string, private http: HttpClient) {
    console.log("Contstructor:" + url);
   }

  getAll(startDate?:string, endDate?:string){

    if(typeof startDate === 'undefined' && typeof endDate === 'undefined')
    {
      return this.http.
                    get<any[]>
                    (
                      this.url
                    )
                    .pipe
                    (
                      catchError
                      (
                        this.handleError
                      )
                    , 
                      timeout
                      (
                        QueryTimeOut
                      )
                    );
    }
    else
    {     
      return this.http
                  .get<any[]>
                  ( 
                    this.url, 
                    {
                      params:
                      {
                        dateStart:startDate, 
                        dateEnd: endDate
                      }
                    }
                  )
                  .pipe
                  (
                    catchError
                    (
                      this.handleError
                    ), 
                    timeout(
                      QueryTimeOut
                      )
                  );
    }

  }

  getAlertsConfig(type?:string){

    if(typeof type === 'undefined')
    {
      return this.http.
                    get<any[]>
                    (
                      this.url
                    )
                    .pipe
                    (
                      catchError
                      (
                        this.handleError
                      )
                    , 
                      timeout
                      (
                        QueryTimeOut
                      )
                    );
    }
    else
    {     
      return this.http
                  .get<any[]>
                  ( 
                    this.url, 
                    {
                      params:
                      {
                        type:type                        
                      }
                    }
                  )
                  .pipe
                  (
                    catchError
                    (
                      this.handleError
                    ), 
                    timeout(
                      QueryTimeOut
                      )
                  );
    }

  }

  getLastRecord(getLastRecord?:boolean)
  {   
      return this.http.get<any[]>(this.url, { params:{
          dateStart:null, 
          dateEnd: null,  getLastRecord: getLastRecord.toString()}  }).pipe(timeout(QueryTimeOut)); 

  }

  getRecord(recordId?:number)
  {   
      return this.http.get<any[]>(this.url + "/" + recordId).pipe(timeout(QueryTimeOut));; 
  }


  create(resource: any){
    return this.http.post<any>(this.url , JSON.stringify(resource))
      .pipe(
        catchError(this.handleError));

  }     

  update(resource:any, isPatch:boolean)
  {

    if(isPatch)
    {
      return this.http.patch(this.url, JSON.stringify({isRead:true}))
        .pipe(
          catchError(this.handleError));
    }
    else
    {
      return this.http.put(this.url, resource );  
    }

  }


  delete(id: number){
    return this.http.delete(this.url+"/"+id)
      .pipe(
        catchError(this.handleError));
  }

  private handleError(error: HttpErrorResponse) {
    console.log(error);
    if (error.error instanceof NotFoundError) {
      // A client-side or network error occurred. Handle it accordingly.
      return throwError(
        'Bad request. Please refresh your page, and try again.');
    } 

    else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong,
      return throwError(
      error.statusText + ": " + error.name );
    }

  };


}

NGINX Configuration:

   server {
                listen 80 default_server;
                listen [::]:80 default_server;

        root /var/www/WebApp;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name _;

        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Headers' 'Content-Type,Accept';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';

  location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
                proxy_pass         http://localhost:5000/;
                proxy_http_version 1.1;
                proxy_set_header   Upgrade $http_upgrade;
                proxy_set_header   Connection keep-alive;
                proxy_set_header   Host $host;
                proxy_cache_bypass $http_upgrade;
                proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header   X-Forwarded-Proto $scheme;

        }

          location /api/FanDataTags {
                proxy_pass         http://localhost:5000/api/FanDataTags;

         }

        location /api/AlertsConfig {
                proxy_pass http://localhost:5000/api/AlertsConfig;
        }
server {
        listen 8080;
        listen [::]:8080;

        server_name _;

        root /var/www/Angular;

        location / {
                #directory, then fall back to displaying a 404.
                try_files $uri $uri/ /index.html;
                index index.html index.htm;
                #proxy_pass         http://localhost:8080/;
                #proxy_http_version 1.1;
                #proxy_set_header   Upgrade $http_upgrade;
                #proxy_set_header   Connection keep-alive;
                #proxy_set_header   Host $host;
                #proxy_cache_bypass $http_upgrade;
                #proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
                #proxy_set_header   X-Forwarded-Proto $scheme;

        }
}

Network Tab in Chrome: NetworkTab

1 Answers

I have found the issue, the problem is that I was configuring CORS policies in two different places, which "the better safe than sorry" mentality is not always correct. The NGINX CORS & the .NET API CORS configuration were getting in conflict.

Add the CORS Data annotation for each method or controller.

[EnableCors(origins: "", headers: "", methods: "*")]

And Configure the CORS Microsoft Nugget Package on the Startup.cs.

    public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              builder =>
                              {
                                  builder.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        services.AddControllers();
    }

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

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

        app.UseCors(MyAllowSpecificOrigins);

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Or use the NGINX whatever you find easier.

Related