2016-11-19 10 views
7

OpenIdDictを使用して保護された.NETコアWeb APIを消費するAngular2 SPAを作成しようとしています。この問題の解決策を作成する際には、すべてのステップをreadmeで詳しく説明していますので、この投稿が私のような初心者に役立つことを願っています。 フルREPROソリューションにこれらのリポジトリでを見つけてください:.NET Core WebAPI + OpenIdDict(資格情報フロー)とAngular2クライアント:401成功後のログイン(フルrepro)

、私はこのフロー(https://github.com/openiddict/openiddict-samples/blob/master/samples/PasswordFlow)についてOpenIdDictによって提供されたサンプルに従いました。ここでは、最も関連性の高いビットがStartupである:私はバイオリンでそれをテストする場合

public void ConfigureServices(IServiceCollection services) 
{ 
    services.AddCors(); 

    services.AddEntityFrameworkSqlServer() 
     .AddDbContext<CatalogContext>(options => 
       options.UseSqlServer(Configuration.GetConnectionString("Catalog"))) 
     .AddDbContext<ApplicationDbContext>(options => 
       options.UseSqlServer(Configuration.GetConnectionString("Catalog"))); 

    services.AddIdentity<ApplicationUser, ApplicationRole>() 
     .AddEntityFrameworkStores<ApplicationDbContext>() 
     .AddDefaultTokenProviders(); 

    services.AddOpenIddict<ApplicationDbContext>() 
     .DisableHttpsRequirement() 
     .EnableTokenEndpoint("/connect/token") 
     .EnableLogoutEndpoint("/connect/logout") 
     .EnableUserinfoEndpoint("/connect/userinfo") 
     .AllowPasswordFlow() 
     .AllowRefreshTokenFlow() 
     .AddEphemeralSigningKey(); 

    services.AddMvc() 
     .AddJsonOptions(options => 
     { 
      options.SerializerSettings.ContractResolver = 
       new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(); 
     }); 

    // add my services 
    // ... 

    services.AddTransient<IDatabaseInitializer, DatabaseInitializer>(); 
    services.AddSwaggerGen(); 
} 

public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
    ILoggerFactory loggerFactory, 
    IDatabaseInitializer databaseInitializer) 
{ 
    loggerFactory.AddConsole(Configuration.GetSection("Logging")); 
    loggerFactory.AddDebug(); 
    loggerFactory.AddNLog(); 

    app.UseDefaultFiles(); 
    app.UseStaticFiles(); 

    app.UseCors(builder => 
      builder.WithOrigins("http://localhost:4200") 
       .AllowAnyHeader() 
       .AllowAnyMethod()); 

    app.UseOAuthValidation(); 
    app.UseOpenIddict(); 
    app.UseMvc(); 
    databaseInitializer.Seed().GetAwaiter().GetResult(); 
    env.ConfigureNLog("nlog.config"); 
    app.UseSwagger(); 
    app.UseSwaggerUi(); 
} 

は、それが正常に動作します:すべての保護されたAPIにアクセスするためのトークン要求がトークンを取得し、私はその後、Authorizationヘッダに含めることができ、 JSONデータを期待どおりに返します。

サンプルトークン要求:

POST http://localhost:51346/connect/token 
Content-Type: application/x-www-form-urlencoded 

grant_type=password&scope=offline_access profile email roles&resource=http://localhost:4200&username=...&password=... 

サンプルリソース要求:

GET http://localhost:51346/api/values 
Content-Type: application/json 
Authorization: Bearer ...received token here... 

しかし、私は同じ要求をしようとするたびに、クライアント側では、私は401エラーを取得します。ログを見ると、Angular2 HttpサービスはエラーAuthentication was skipped because no bearer token was received(以下のログエントリを参照)を受け取るため、必要なヘッダーをまったく送信していないようです。

いくつかのリソースを検索するサービスは次のようである:

import { Injectable } from '@angular/core'; 
import { Http, Response } from '@angular/http'; 
import { Observable } from 'rxjs/Observable'; 
import { SettingsService } from './settings.service'; 
import { AuthenticationService } from './authentication.service'; 

export interface ICategory { 
    id: string; 
    name: string; 
} 

@Injectable() 
export class CategoryService { 

    constructor(
    private _http: Http, 
    private _settings: SettingsService, 
    private _authService: AuthenticationService) { } 

    public getCategories(): Observable<ICategory[]> { 
    let url = this._settings.apiBaseUrl + 'categories'; 
    let options = { 
     headers: this._authService.createAuthHeaders({ 
     'Content-Type': 'application/json' 
     }) 
    }; 
    return this._http.get(url, options).map((res: Response) => res.json()) 
     .catch((error: any) => Observable.throw(error.json().error || 'server error')); 
    } 
} 

ヘルパーcreateAuthHeadersは単に、Headerhttps://angular.io/docs/ts/latest/api/http/index/Headers-class.html)エントリを表すいくつかのプロパティを取得し格納されたトークンを取得し、ヘッダにAuthenticationエントリを追加し、そしてそれを返す:

さらに
public createAuthHeaders(headers?: { [name: string]: any }): Headers { 
    let auth = new Headers(); 
    if (headers) { 
     for (let key in headers) { 
      if (headers.hasOwnProperty(key)) { 
       auth.append(key, headers[key]); 
      } 
     } 
    } 
    let tokenResult = this._localStorage.retrieve(this._settings.tokenStorageKey, true); 
    if (tokenResult) { 
     auth.append('Authentication', 'Bearer ' + tokenResult.access_token); 
    } 
    return auth; 
} 

、この要求は、401レスポンスを取得し、JSONオブジェクトに対する応答をマッピングしようとすると、その後角度は(スロー)。

私は、クライアントがトークンを取得するとすぐに、それが別の要求を出してユーザー情報を取得することを追加しなければなりません。ここでは、(get user info後にコードを参照)である。

public login(name: string, password: string) { 
    let body = 'grant_type=password&scope=offline_access profile email roles' + 
     `&resource=${this._settings.appBaseUrl}&username=${name}&password=${password}`; 

    this._http.post(
     this._settings.authBaseUrl + `token`, 
     body, 
     { 
      headers: new Headers({ 
       'Content-Type': 'application/x-www-form-urlencoded' 
      }) 
     }).map(res => res.json()) 
     .subscribe(
     (token: ITokenResult) => { 
      if (token.expires_in) { 
       token.expires_on = this.calculateExpirationDate(+token.expires_in); 
      } 
      this._localStorage.store(this._settings.tokenStorageKey, token, true); 
      // get user info 
      this._http.get(this._settings.authBaseUrl + 'userinfo', { 
       headers: new Headers({ 
        'Content-Type': 'application/json', 
        'Authorization': 'Bearer ' + token.access_token 
       }) 
      }).map(res => res.json()) 
       .subscribe((info: IUserInfoResult) => { 
        let user: IUser = { 
         id: info.name, 
         email: info.email, 
         name: info.name, 
         firstName: info.given_name, 
         lastName: info.family_name, 
         role: info.role, 
         verified: info.email_verified 
        }; 
        this._localStorage.store(this._settings.userStorageKey, user, true); 
        this.userChanged.emit(user); 
       }, error => { 
        console.log(error); 
       }); 
     }, 
     error => { 
      console.log(error); 
     }); 
    } 

しかし、他の要求、上記のサービスを使用して構築され、失敗しました。クォートされた関数で構築されたヘッダーで何が問題になっていますか?ここで

は、サーバー側でいくつかのログエントリは、次のとおりです。

2016-11-18 20:41:31.9815|0|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|DEBUG| Authentication was skipped because no bearer token was received. 
2016-11-18 20:41:31.9815|0|OpenIddict.Infrastructure.OpenIddictProvider|INFO| The token request validation process was skipped because the client_id parameter was missing or empty. 
2016-11-18 20:41:32.0715|0|AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerMiddleware|INFO| No explicit audience was associated with the access token. 
2016-11-18 20:41:32.1165|10|AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerMiddleware|INFO| AuthenticationScheme: ASOS signed in. 
2016-11-18 20:41:32.1635|3|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|INFO| HttpContext.User merged via AutomaticAuthentication from authenticationScheme: Bearer. 
2016-11-18 20:41:57.7430|0|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|DEBUG| Authentication was skipped because no bearer token was received. 
2016-11-18 20:41:57.7430|0|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|DEBUG| Authentication was skipped because no bearer token was received. 
2016-11-18 20:41:57.8820|12|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|INFO| AuthenticationScheme: Bearer was challenged. 
2016-11-18 20:41:57.9305|12|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|INFO| AuthenticationScheme: Bearer was challenged. 
2016-11-18 20:41:57.9465|0|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|DEBUG| Authentication was skipped because no bearer token was received. 
2016-11-18 20:41:57.9925|12|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|INFO| AuthenticationScheme: Bearer was challenged. 
+0

このプロジェクトを最近更新しましたか? OpenIdDictの新しいバージョンがあり、あなたのサンプルはもう動作しません。 ApplicationDbContextに_context.Applications.Any()がもうないので、DatabaseInitializer.Seed()内で失敗しています... – marrrschine

+0

皆さん、新しくリリースされたOpenIdDictと互換性があるように、サンプルリポジトリを更新しました。それを確認してください、私は実際のアプリケーションでテストしていませんが、サンプルは再び動作するようです。私はそれに応じてreadmeを更新しましたが、何かを忘れてしまった可能性があるので、ソースコードを確認してください。 – Naftis

+0

すごい!どうもありがとうございました。残念ながら、私はダムの質問の束を持っています。どのような情報をスコープの中に入れることができますか?どのような目的のためにUserSecretsを使用していますか? JWTに切り替えるのは大変なことでしょうか?このプロジェクトを自己完結型のexeとして実行するにはどうすればよいですか?おかげで再び – marrrschine

答えて

3

あなたのベアラトークンの使用が正しくありません。

auth.append('Authentication', 'Bearer ' + tokenResult.access_token) // wrong 
auth.append('Authorization', 'Bearer ' + tokenResult.access_token) // right 

ヘッダーはAuthorizationである必要があります。 https://tools.ietf.org/html/rfc6750#section-2.1

+0

うわー、私はとても盲目だったとは信じられません。すべての事をセットアップするのに何時間も費やしていましたが、私が別の単語を交換したことに気付かなかったのです:)。とにかく、これが.NETのコアとAngular2の認証サンプルを探している人にとって有益であることを願っています。 – Naftis

+0

@Naftisこれは、OpenID ConnectにHTTPを使用することで起こったエラーを見つけるのに32時間を思い出しましたリクエストとHTTPSを返します。 –

+0

共有ありがとう! – marrrschine

関連する問題