.NET Core WebAPI + OpenIdDict (credential stream) and Angular2 client: 401 after successful login (full play)

I am trying to create an Angular2 SPA using the .NET Core Web API protected by OpenIdDict with a credential stream. When creating a solution to reproduce this problem, I also described in detail all my steps in readme, so I hope this post can be useful for beginners like me. Please find complete reproducing solutions in these repositories:

As for the server side, I followed the example provided by OpenIdDict about this thread ( https://github.com/openiddict/openiddict-samples/blob/master/samples/PasswordFlow ). Here are the most important bits in 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(); } 

If I test it with Fiddler, it works fine: the token request receives a token, and then I can include it in the Authorization header to access any secure API that returns JSON data as expected.

Token request example:

 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=... 

Example resource request:

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

However, on the client side, when I try to execute the same request, I get error 401; looking at the log, it seems that the Angular2 Http service does not send the requested header at all, since I get the Authentication was skipped because no bearer token was received error message Authentication was skipped because no bearer token was received (see more log entries below).

A service that receives some resources is as follows:

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

If the createAuthHeaders helper just gets some properties representing Header entries ( https://angular.io/docs/ts/latest/api/http/index/Headers-class.html ), retrieves the stored token, adds an Authentication entry to the header, and returns his:

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

However, this request receives a 401 response, and then Angular throws it when trying to match the response to a JSON object ( Unexpected end of JSON input ).

I must add that as soon as the client receives the token, it makes another request with it to get information about the user, and this works fine; here it is (see code after 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); }); } 

However, any other request built using the above service fails. What happened to the headers constructed with the quoted function?

Here are some server side log entries:

 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. 
+7
authentication angular asp.net-web-api openiddict
source share
1 answer

Misuse of bearer token.

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

The title should be Authorization . See https://tools.ietf.org/html/rfc6750#section-2.1

+3
source share

All Articles