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?
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;
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With