How to Configure Angular 6 with .NET Core 2 to allow CORS from any host?
Possible Issues:
- In your
startup.cs.Configure()
method, doesapp.UseCors()
precedeapp.useMVC()
? - Does the Http Request URL contain a trailing slash
(/)
? - Does the Http Request include Credentials?
- Is the browser receiving a Http Response from the API?
- Does the Response include a 'Access-Control-Allow-Origin' header?
- When you send the request from Postman, does the Http Response include the 'Access-Control-Allow-Origin' header as well as a body containing the data?
Gotchas
Firefox requires a certificate to be installed for your API in order to send Http Request utilizing the HTTPS protocol.
Test your API with Postman and your browser Developer Tool. Notice 2 Http requests. The Http 200 is a “pre-flight” to see what CORS options are available.
- If your API (.NET) throws a
HTTP 500 (Internal Server Error)
, it will return a developer exception page and Postman will display a“no ‘Access-Control-Allow-Origin’ header is present on the requested resource”
message - this is mis-leading. - The actual problem in this case is that the developer exception page cannot be returned from the server to a client (browser) running on a different Origin.
I would like to respectfully point out the following:
- The CORS specification states that setting origins to '*' (all origins) is invalid if the Access-Control-Allow-Credentials header is present.
AllowAnyOrigin()
is not recommended in production unless you intend to allow anyone to utilize your API AND you will not implement credentials.- You do not need to configure CORS at the controller level with the
EnableCors
attribute when utilizing multiple CORS policies. - Configuring multiple CORS policies with ASP.NET Core is easy(see tutorial below)
The following articles are worth reviewing:
ASP.NET Core 2.2 does not permit allowing credentials with AllowAnyOrigin()
Enable Cross-Origin Requests (CORS) in ASP.NET Core
key points (tldr;):
- CORS Middleware must precede any defined endpoints in your app configuration.
- The URL must be specified without a trailing slash
(/)
. - If the browser sends credentials but the response doesn't include a valid Access-Control-Allow-Credentials header, the browser doesn't expose the response to the app, and the cross-origin request fails.
- The CORS specification also states that setting origins to "*" (all origins) is invalid if the Access-Control-Allow-Credentials header is present.
- If a browser supports CORS, it sets these headers automatically for cross-origin requests.
- If the response doesn't include the Access-Control-Allow-Origin header, the cross-origin request fails.
CORS Tutorial: (2) Angular Clients + ASP.NET Core
Create Visual Studio Solution
md c:\s\a cd c:\s\a c:\s\a>dotnet new sln -n solutionName
Create ASP.NET Core Project
c:\s\a>md s c:\s\a>cd s c:\s\a\s>dotnet new webapi -o api -n api
API Launch Settings and CORS Configuration
launchSettings.json
Clone Development Profile to Staging Profile
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iis": {
"applicationUrl": "http://localhost:myIISApiPortNumber",
"sslPort": myIISApiSSLPortNumber
},
"iisExpress": {
"applicationUrl": "http://localhost:myIISExpressApiPortNumber",
"sslPort": myIISExpressApiSSLPortNumber
}
},
"profiles": {
"Development (IIS Express)": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Staging (IIS Express)": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/values",
"applicationUrl": "https://localhost:myIISExpressApiSSLPortNumber;http://localhost:myIISExpressApiPortNumber",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Staging"
}
},
"Production (IIS)": {
"commandName": "IIS",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
},
"applicationUrl": "https:localhost:myIISApiSSLPortNumber;http://localhost:myIISApiPortNumber"
}
}
}
startup.cs
Add CORS Configuration
public class Startup
{
public IConfiguration Configuration { get; }
public IServiceCollection _services { get; private set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
_services = services;
RegisterCorsPolicies();
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseCors("DevelopmentCorsPolicy");
app.UseDeveloperExceptionPage();
}
else if (env.IsStaging())
{
app.UseCors("StagingCorsPolicy");
}
else
{
app.UseCors("ProductionCorsPolicy");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc(); // CORS middleware must precede any defined endpoints
}
private void RegisterCorsPolicies()
{
string[] localHostOrigins = new string[] {
"http://localhost:4200", "http://localhost:3200"};
string[] stagingHostOrigins= new string[] {
"http://localhost:4200"};
string[] productionHostOrigins = new string[] {
"http://yourdomain.net", "http://www.yourdomain.net",
"https://yourdomain.net", "https://www.yourdomain.net"};
_services.AddCors(options => // CORS middleware must precede any defined endpoints
{
options.AddPolicy("DevelopmentCorsPolicy", builder =>
{
builder.WithOrigins(localHostOrigins)
.AllowAnyHeader().AllowAnyMethod();
});
options.AddPolicy("StagingCorsPolicy", builder =>
{
builder.WithOrigins(stagingHostOrigins)
.AllowAnyHeader().AllowAnyMethod();
});
options.AddPolicy("ProductionCorsPolicy", builder =>
{
builder.WithOrigins(productionHostOrigins)
.AllowAnyHeader().AllowAnyMethod();
});
//options.AddPolicy("AllowAllOrigins",
// builder =>
// {
// WARNING: ASP.NET Core 2.2 does not permit allowing credentials with AllowAnyOrigin()
// cref: https://docs.microsoft.com/en-us/aspnet/core/migration/21-to-22?view=aspnetcore-2.2&tabs=visual-studio
// builder.AllowAnyOrigin()
// .AllowAnyHeader().AllowAnyMethod();
// });
//options.AddPolicy("AllowSpecificMethods",
// builder =>
// {
// builder.WithOrigins(productionHostOrigins)
// .WithMethods("GET", "POST", "HEAD");
// });
//options.AddPolicy("AllowSpecificHeaders",
// builder =>
// {
// builder.WithOrigins(productionHostOrigins)
// .WithHeaders("accept", "content-type", "origin", "x-custom-header");
// });
//options.AddPolicy("ExposeResponseHeaders",
// builder =>
// {
// builder.WithOrigins(productionHostOrigins)
// .WithExposedHeaders("x-custom-header");
// });
//options.AddPolicy("AllowCredentials",
// WARNING: ASP.NET Core 2.2 does not permit allowing credentials with AllowAnyOrigin() cref: https://docs.microsoft.com/en-us/aspnet/core/migration/21-to-22?view=aspnetcore-2.2&tabs=visual-studio
// builder =>
// {
// builder.WithOrigins(productionHostOrigins)
// .AllowCredentials();
// });
//options.AddPolicy("SetPreflightExpiration",
// builder =>
// {
// builder.WithOrigins(productionHostOrigins)
// .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
// });
});
}
}
- Start Debugging API with Development (IIS Express) profile
Set breakpoint on ValuesController.Get()
Test API with Postman:
https://localhost:myApiPortNumber/api/values
- Access-Control-Allow-Origin header and values should be in response
Create Angular application
c:\s\a\s>ng new Spa1 --routing (will automatically create Spa folder)
Start Spa1 application
c:\s\a\s>cd Spa1 c:\s\a\s\Spa1>Ng serve
Browse to http://localhost:4200/
- Spa1 should start successfully
Implement CORs in Spa1
app.module.ts
- Import HttpClientModule
import { HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
HttpClientModule,
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
Import HttpClient
Add
getValues()
method with CORS Request
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'Spa1';
values: any;
apiUrl: string = environment.apiUrl;
valuesUrl = this.apiUrl + "values";
constructor(private http: HttpClient) { }
ngOnInit() {
this.getValues();
}
getValues() {
this.http.get(this.valuesUrl).subscribe(response => {
this.values = response;
}, error => {
console.log(error);
});
}
}
app.component.html
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
</div>
<h2>Values</h2>
<p *ngFor="let value of values">
{{value}}
</p>
<router-outlet></router-outlet>
environment.ts
export const environment = {
production: false,
apiUrl: 'https://localhost:myApiPortNumber/api/'
};
Start Spa1 application
c:\s\a\s\Spa1>Ng serve
Browse to http://localhost:4200/
- Chrome & Edge: should successfully make CORs requests now
- Safari: I have not tested
- Firefox: may block request due to un-trusted certificate.
One way of remediating the Firefox block:
FireFox | Options | Privacy & Security | Security | Certificates | [View Certificates] :
Certificate Manager | [Add Exception]:
add localhost
CORS Test
Clone Spa1
c:\s\a\s>xcopy /s /i Spa1 Spa2
Refactor title of Spa2
app.component.ts
export class AppComponent implements OnInit {
title = 'Spa2';
}
Start Spa2 application on port 3200
c:\s\a\s\Spa2>ng serve --port 3200
Browse to http://localhost:3200/
- The values array should render on the web page
Stop Debugging API with Development (IIS Express) profile
Start Debugging API with Staging (IIS Express) profile
Browse to http://localhost:4200/
- The values array should render on the web page.
Browse to http://localhost:3200/
- The values array should not render on the web page.
inspect Http Response with Developer Tools:
- Access-Control-Allow-Origin header and values should not be in response
Add the following code snippet in the method ConfigureServices. Edit this for allowing only custom headers.
// Add service and create Policy with options
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
});
Add the following in Configure method
app.UseCors("CorsPolicy");
Add 'EnableCors' attribute to the controller.
[EnableCors("CorsPolicy")]
Register cors in services
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.SetIsOriginAllowed((host) => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
add cors middleware
app.UseCors("CorsPolicy");