2017年12月27日 星期三

2018 IT邦幫忙鐵人賽 - 以三十個需求的實作來學習設計模式

 鐵人賽    IT邦幫忙      Design Pattern  


前言


第二次參加鐵人賽,主題是:

Learning Design Pattern in 30 real-case practices
以三十個需求的實作來學習設計模式

顧名思義,就是用實例來探討設計模式如何應用,自從2013年學習了設計模式也記錄了一些相關的文章 回頭看一下自己2013年寫的文章,感覺太多細節沒有說到,也用了太簡單的例子來說明; 導致自己也看了很模糊的情況(囧)

所以毅然決然用這次的機會再重新學習並記錄設計模式,目標是能多深入一些(但不要流於理論)及提供更貼近現實需求的範例代碼。

感謝創造設計模式的GoF,它讓我覺得寫程式不再是苦力,而是像藝術家或建築師一樣,可以設計出美麗和風格的代碼!


文章列表


以下連結將連至2018 IT邦幫忙鐵人賽,文章也同步發表在Github上,如有建議或錯誤請您不吝批評指教!

Day 1
Day 2
Day 3
Day 4
Day 5
Day 6
Day 7
Day 8
Day 9
Day 10
Day 11
Day 12
Day 13
Day 14
Day 15
Day 16
Day 17
Day 18
Day 19
Day 20
Day 21
Day 22
Day 23
Day 24
Day 25
Day 26
Day 27
Day 28
Day 29
Day 30





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