I have a simple Angular App running locally on 4200 i am testing with a web api running locally on 5000 using .net core. My startup.cs has CORS configured correctly to allow everything i believe.
In the ConfigureServices section i have:
services.AddCors();
In the Configure section i have:
app.UseCors(options => options.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
However when i try and hit my webapi i still get this in the browser.
from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
I have looked at the other answers and they all seem to have this or similar variations that i have tried with no change. Not sure what else i need to do?
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")]
Possible Issues:
- In your
startup.cs.Configure()
method, does app.UseCors()
precede app.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 } 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 { 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
Register cors in services
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.SetIsOriginAllowed((host) => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
add cors middleware
app.UseCors("CorsPolicy");
I ran in the same problem but i my case i had windowsauthentification.
@RandyDaddis pointed me to the right direction.
You can't use '*' if authentification is set.
But this doesn't solve the hole problem (and for me '*' is not a good option).
I had to changed from windowsAuthentication only to a mixed mode (where anonymousAuthentication is also enabled) because the preflight was not sending the credentials.
This was already an discussed issue by asp.
Important is that you add this to your startup.cs in the ConfigureServices function to ensure all your controllers are still under authorization-policy:
...
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})...