2017年12月16日 星期六

[Angular] Windows authentication with Web API

 Angular    ASP.NET Core     Web API    Windows Authentication


Introduction


This is a tutorial of how to set windows authentication on ASP.NET Core Web API and send a request with Angular to get the current windows user. There will 4 parts in the article.

1.  Enable windows authentication on ASP.NET Core Web API(on IIS integration or HTTP.sys)
2.  Angular: fail try
3.  Angular: solve problem with Http Interceptors



Environment


Angular 5.0.0
ASP.NET Core 2.0
Windows 7




Implement: Enable windows authentication


Enable windows authentication on ASP.NET Core Web API

Enable windows authentication by putting the following settings to appsettings.json.

appsettings.json

"iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": true
}

Set "anonymousAuthentication" to false if you want to deny the request from anonymous user.

Or you can easily enable windows authentication in Visual Studio like following.






The settings are stored in ./Prperties/launchSettings.json

appsettings.json

"iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:9486/",
      "sslPort": 0
    }
  }



For HTTP.sys

Program.cs

public static void Main(string[] args)
{
    BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .UseHttpSys(options =>
                {
                    options.Authentication.Schemes =
                        AuthenticationSchemes.NTLM | AuthenticationSchemes.Negotiate;
                    options.Authentication.AllowAnonymous = true;
                })
                .Build();
}


Startup.cs

public void ConfigureServices(IServiceCollection services)
{
  services.AddMvc();
    services.AddAuthentication(HttpSysDefaults.AuthenticationScheme);
}



For IIS integration

Program.cs

public static void Main(string[] args)
{
    BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .UseIISIntegration()
                .Build();
}


Startup.cs

public void ConfigureServices(IServiceCollection services)
{
  services.AddMvc();
    services.AddAuthentication(
Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme);
}



Now let’s write a HttpGet method for later testing.

[HttpGet("IsAdmin")]
 [Authorize]
public async Task<bool> IsAdmin()
{
             
      var userId = HttpContext.User.Identity.Name;
      base._logger.Info($"Windows User for request : {userId}");
      return true;
}



Implement: Angular


Service & Component

Notice that we have to send the request with http header:

withCredentials: true

to make the request a user credential, like a cookie, to the server side.


MyService.ts

import { HttpClient, HttpHeaders } from "@angular/common/http";

export class YearEndService {

  private httpHeaders: HttpHeaders;

  constructor(
    private http: HttpClient,
    private uriService: RestUriService) {

    this.httpHeaders = new HttpHeaders({
      'Content-Type': 'application/json',
      'withCredentials': 'true'
    });

  }
  public isAdmin() {
    return this.http.get("http://localhost:3214/api/blablabla", {headers:this.httpHeaders});
  }
}


MyComponent.ts

export class MyComponent implements OnInit {

  constructor() {
}

  ngOnInit() {
    this.ypService.isAdmin().subscribe(rtn => {})
  }
}


The above codes will result in HTTP 401 Unauthorized on Chrome or Firefox, but works fine on IE.




Why not working on Chrome/Firefox

Chrome and Firefox ignore sending user credential after the pre-flight request.

Incorrect request (Chrome):




But the success request must be like …





Solve the problem by HTTP Interceptors

We will attach the withCredentials header to every request by HTTP Interceptors to guarantee sending user credential on the request.

First create a WinAuthInterceptor to implement HttpInterceptor

WinAuthInterceptor.ts

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpInterceptor, HttpEvent} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class WinAuthInterceptor implements HttpInterceptor {
  constructor() {
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    // request = request.clone({ headers: request.headers.set('withCredentials', 'true') });
    request = request.clone({
      withCredentials: true
    });
    return next.handle(request);
  }
}



app.module.ts

Enable our Http Interceptor on every request like following.

import { HttpClientModule  } from '@angular/common/http';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { WinAuthInterceptor } from './class/winAuthInterceptor';

@NgModule({
  declarations: [
    //…
  ],
  imports: [
    HttpClientModule,
    AppRouteModule
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: WinAuthInterceptor,
      multi: true
    }
],
  //…
})
export class AppModule { }



MyService.ts

public isAdmin() {
    return this.http.get("http://localhost:3214/api/blablabla");
  }


The Http Interceptor will solve the problem of losing user credentials in Chrome/Firefox.



Reference




沒有留言:

張貼留言