My IDE is Visual Studio 2017. I've got an Angular4 client talking to a WebAPI backend in Core, and CORS is working as configured EXCEPT for the PUT and POST methods. The GET method is subject to the same preflight OPTIONS method in Chrome that the PUT and POST methods are, but GET is working fine.
It appears that the IIS Express server in Visual Studio is not forwarding the requests to the Kestrel server. Both Methods work in Postman, but not when Angular4 makes the call. Here's the code:
Angular4 POST
post(api: string, object: any): Observable<any> {
let body = JSON.stringify(object);
let options = new RequestOptions({
headers: this.headers,
withCredentials: true
});
return this.http.post(this.server + api, body, options)
.map((res: Response) => res.json())
.catch((error: any) => Observable.throw(error.json().error) || 'Post server error');
}
Startup.cs Configure
services.Configure<IISOptions>(options =>
options.ForwardWindowsAuthentication = true);
services.AddCors(options => {
options.AddPolicy("AllowAll", builder => {
builder.WithOrigins("http://localhost:XXXX")
.WithMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.WithHeaders("Origin", "X-Requested-With", "Content-Type", "Accept", "Authorization")
.AllowCredentials();
});
});
Startup.cs ConfigureServices
app.UseCors("AllowAll");
IIS ApplicationHost.Config in Project
<anonymousAuthentication enabled="false" userName="" />
<basicAuthentication enabled="false" />
<clientCertificateMappingAuthentication enabled="false" />
<digestAuthentication enabled="false" />
<iisClientCertificateMappingAuthentication enabled="false"></iisClientCertificateMappingAuthentication>
<windowsAuthentication enabled="true" >
<providers>
<add value="Negotiate" />
</providers>
</windowsAuthentication>
AND
<customHeaders>
<clear />
<add name="X-Powered-By" value="ASP.NET" />
<add name="Access-Control-Allow-Origin" value="http://localhost:5000"/>
<add name="Access-Control-Allow-Headers" value="Accept, Origin, Content-
Type"/>
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE,
OPTIONS"/>
<add name="Access-Control-Allow-Credentials" value="true"/>
</customHeaders>
Response for GET
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: **Kestrel**
X-SourceFiles: =?UTF-8?B?QzpcZGV2cHJvamVjdHNcVFJXRC5IeWRyb21hcnRBZG1pblxKVF9BZGRUYWdNYW5hZ2VtZW50XFRSV0QuSHlkcm9NYXJ0LkFwcFxhcGlcdGFncw==?=
Persistent-Auth: true
X-Powered-By: ASP.NET
Access-Control-Allow-Origin: http://localhost:5000
Access-Control-Allow-Headers: Accept, Origin, Content-Type
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Credentials: true
Date: Fri, 14 Jul 2017 17:03:43 GMT
Response for POST
HTTP/1.1 401 Unauthorized
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-SourceFiles: =?UTF-8?B?QzpcZGV2cHJvamVjdHNcVFJXRC5IeWRyb21hcnRBZG1pblxKVF9BZGRUYWdNYW5hZ2VtZW50XFRSV0QuSHlkcm9NYXJ0LkFwcFxhcGlcdGFncw==?=
WWW-Authenticate: Negotiate
X-Powered-By: ASP.NET
Access-Control-Allow-Origin: http://localhost:5000
Access-Control-Allow-Headers: Accept, Origin, Content-Type
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Credentials: true
Date: Fri, 14 Jul 2017 17:05:11 GMT
Content-Length: 6095
So the big question is, what am I missing?
Here is what worked for me...
First, add a CORS policy to ConfigureServices() in Startup.cs
services.AddCors(o => o.AddPolicy("CORSPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}));
and use in the Configure() method
app.UseCors("CORSPolicy");
Next, either add Authorize attributes to your controllers or add them globally. I went with globally. To do that, add the following after the services.AddCors() method above in the ConfigureServices() method (you can have other stuff inside that predicate, too)
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
Then, I added this little nugget (it's what made everything "just work" for me). Basically, it allows preflight requests to pass through IIS without authenticating.
services.AddAuthentication(IISDefaults.AuthenticationScheme);
I think you'll have to add the following to your usings, too
using Microsoft.AspNetCore.Server.IISIntegration;
You should also tell the app to use authentication. Do this in the Configure() method below app.UseCors("CORSPolicy")
app.UseAuthentication();
Finally, make sure you have anonymousAuthentication and windowsAuthentication set to true in your applicationhost.config file. If you don't know what that file is, it configures your IIS settings for the app. You can find it in the hidden .vs folder at the root of your project inside the config folder.
<authentication>
<anonymousAuthentication enabled="true" userName="" />
<basicAuthentication enabled="false" />
<clientCertificateMappingAuthentication enabled="false" />
<digestAuthentication enabled="false" />
<iisClientCertificateMappingAuthentication enabled="false"></iisClientCertificateMappingAuthentication>
<windowsAuthentication enabled="true">
<providers>
<add value="Negotiate" />
<add value="NTLM" />
</providers>
</windowsAuthentication>
</authentication>
I don't know if this is the case for everyone, but I have two <authentication></authentication> sections in my applicationhost.config file. I changed both to be on the safe side and have not tried changing one and not the other.
**
TLDR;
**
STEP 1)
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy("CORSPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}));
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("CORSPolicy");
app.UseAuthentication();
app.UseMvc();
}
STEP 2) in applicationhost.config You may have to do this in TWO places in the file
<authentication>
<anonymousAuthentication enabled="true" />
<windowsAuthentication enabled="true" />
</authentication>
Got it. Okay, so basically, what was happening was a preflight OPTIONS request doesn't have authorization on it, so it was by default and design, failing since I had disabled anonymous authentication and enabled windows authentication. I had to allow anonymous authentication to occur to both the client and the web api so that the OPTIONS requests could get through unscathed. This, however, leaves a huge security hole that I had to resolve. Since I had opened the door to the OPTIONS requests, I had to close that door somehow for the POST, PUT and DELETE requests. I did this by creating an authorization policy that only allowed in authenticated users. My final code is as follows:
Angular 4 Post
Note the use of withCredentials in the options.
post(api: string, object: any): Observable<any> {
let body = JSON.stringify(object);
let options = new RequestOptions({
headers: this.headers,
withCredentials: true
});
return this.http.post(this.server + api, body, options)
.map((res: Response) => res.json())
.catch((error: any) => Observable.throw(error.json().error) || 'Post server error');
}
Startup.cs
Added CORS, added an authentication policy, used CORS.
(under ConfigureServices)
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.WithOrigins("http://localhost:5000")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
and
services.AddAuthorization(options =>
{
options.AddPolicy("AllUsers", policy => policy.RequireAuthenticatedUser());
});
and
(under Configure)
app.UseCors("AllowSpecificOrigin");
Controller
Added authorization referencing the policy created in the Startup.
[Authorize(Policy = "AllUsers")]
[Route("api/[controller]")]
public class TagsController : ITagsController
applicationhost.config for IISExpress
<authentication>
<anonymousAuthentication enabled="false" userName="" />
<basicAuthentication enabled="false" />
<clientCertificateMappingAuthentication enabled="false" />
<digestAuthentication enabled="false" />
<iisClientCertificateMappingAuthentication enabled="false"></iisClientCertificateMappingAuthentication>
<windowsAuthentication enabled="true">
<providers>
<add value="Negotiate" />
<add value="NTLM" />
</providers>
</windowsAuthentication>
</authentication>
I totally removed the custom headers.
This solution allowed all 4 verbs to work as expected and I was able to use the identifying information in the httpContext.User object to log information to the database.
Once I deploy this to IIS, I expect will have to add the forwardWindowsAuthToken to the web.config:
<aspNetCore processPath=".\TRWD.HydroMart.App.exe" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="true" />
and
this to the startup in ConfigureServices:
services.Configure<IISOptions>(options => {
options.ForwardWindowsAuthentication = true;
});
I got it working with very simple approach. I uses dotNet Core 2.0.
In configureServices method of startup
services.AddCors(options =>
{
options.AddPolicy("app-cors-policy",
builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
;
});
});
In configure method of startup
app
.UseCors("app-cors-policy") //Must precede UseMvc
.UseMvc();
Then, enable both - Anonymous authentication and Windows authentication as well. This allows OPTIONS requests (preflight) which are anonymous always, to go through. All xhr requests has to be withCredentials else will fail.
To Allow anonymous preflight options you can add the following to your web.config
<security>
<authorization>
<remove users="*" roles="" verbs="" />
<add accessType="Allow" users="" roles="mydomain/mygroup" />
<add accessType="Allow" users="?" verbs="OPTIONS" />
</authorization>
</security>
Note: the use of a group to limit the access for authenticated users and limiting the access of anonymous users to the OPTIONS verb.
Add the cors package Microsoft.AspNetCore.Cors if you haven't already.
And configure it.
A good guide for configuring CORS can be found here:
来源:https://stackoverflow.com/questions/45108307/angular4-asp-net-core-1-2-windows-authentication-cors-for-put-and-post-gives-401