Angular4 ASP.NET Core 1.2 Windows Authentication CORS for PUT and POST Gives 401

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;
    });