I am trying to make a request from my Angular front end to Canada Posts Get Rate API. Unfortunately my backend .Net Core 2.2 seems to be blocking all preflight requests. If I try to issue these requests using a chrome browser with web security flag disabled I am able to complete the request, but unfortunately on normal browsing I am not able to complete the requests. I have tried reading many threads on here, and making cors exceptions specified in the .NET docs but it seems preflight requests are still being blocked.
Error Message: Access to XMLHttpRequest at 'https://soa-gw.canadapost.ca/rs/ship/price' 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.
Some of the Documents I've reffered to without success, I've tried adding additional cors permissions to C# but preflight requests are still being blocked. https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-5.0 https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api#how-cors-works
I'm also unable to modify front end http headers to remove preflight check because some of these are required by canada post api. https://origin-www.canadapost.ca/info/mc/business/productsservices/developers/services/rating/getrates/default.jsf
Front End Call
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/vnd.cpc.ship.rate-v4+xml', //<- To SEND XML
'Accept': 'application/vnd.cpc.ship.rate-v4+xml', //<- To ask for XML //<- b/c Angular understands text
'Authorization': 'Basic YTUxZGQ1MWY2YjJhNjE4ODowODgzOThiZTUxY2Y4Y2RjZTIzM34',
'Accept-language': 'en-CA',
'Access-Control-Allow-Origin': '*',
"Access-Control-Allow-Methods": "DELETE, POST, GET, OPTIONS",
}),
responseType: 'text' as 'json'
};
var postedData = `
<mailing-scenario xmlns="https://www.canadapost.ca/ws/ship/rate-v4">
<customer-number>0009669089</customer-number>
<parcel-characteristics>
<weight>5 </weight>
</parcel-characteristics>
<origin-postal-code>M5V0G1</origin-postal-code>
<destination>
<domestic>
<postal-code>M4R1L8</postal-code>
</domestic>
</destination>
</mailing-scenario>
`;
// http post to canada post using angular httpclient
this.http.post('https://soa-gw.canadapost.ca/rs/ship/price', postedData, httpOptions)
.subscribe(
result => {
console.log(result);
},
error => console.log('There was an error: ', error));
Backend Startup.cs
using AutoMapper;
using Domain;
using Domain.Entities;
using Domain.Entities.Projects;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Rolox.Domain.Entities;
using Server.Classes;
using Server.Interfaces;
using Server.Models;
using System;
using System.IO;
namespace Server
{
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.WithOrigins("*")
.AllowAnyHeader()
.AllowAnyMethod();
});
options.AddPolicy("MyAllowAllHeadersPolicy",
builder =>
{
builder.WithOrigins("*").AllowAnyMethod()
.AllowAnyHeader();
});
});
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddSwaggerDocument();
services.AddDbContext<RoloxDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
providerOptions => providerOptions.EnableRetryOnFailure()));
services.AddDefaultIdentity<User>()
.AddRoles<Role>()
.AddEntityFrameworkStores<RoloxDbContext>();
/// Auto Mapper Configurations
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
ConfigureRepositories(services);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.Configure<SettingModel>(Configuration.GetSection("Settings"));
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;
});
services.Configure<FormOptions>(options =>
{
options.MemoryBufferThreshold = Int32.MaxValue;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors("MyAllowAllHeadersPolicy");
app.UseOpenApi();
app.UseSwaggerUi3();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
});
}
private void ConfigureRepositories(IServiceCollection services)
{
/// Repository Injections
services.AddScoped<IRoloxRepository<ProposalStage>, ProposalStageRepository>();
services.AddScoped<IProposalRepository, ProposalRepositoy>();
services.AddScoped<IRoloxRepository<ChecklistItem>, ChecklistRepository>();
services.AddScoped<IRoloxRepository<Contractor>, ContractorRepository>();
services.AddScoped<IRoloxRepository<Project>, ProjectRepository>();
services.AddScoped<IDirectCostRepository, ProjectDirectCostRepository>();
services.AddScoped<IInvoiceRepository, InvoiceRepositoy>();
services.AddScoped<ICompanyProfileRepository, CompanyRepository>();
}
}
}
Solution: to send message from production, front end preflight request are blocked, send api call from backend server instead. Despite this working on testing with web security disable in live use it must be sent from the back end.