How to implement Windows Authentication in an Angular (^4.3.1) application with a stand-alone Web API

I’ve noticed that my post about Windows Authentication in an AngularJS application has gotten a lot of attention.

With the new HttpClient introduced in Angular 4.3.1, I think it’s a good moment to write a little update.

I’m following the same setup as the previous post:

  • Angular project
  • Web Api project
  • Windows Authentication

Let’s get started.

Web Api

Little has changed for the Web Api part. The short version is:

  • application.config
    <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>
    
  • Web API
    • Add CORS
    • Configure CORS
      var cors = new EnableCorsAttribute("http://localhost:30033", "*", "*") { SupportsCredentials = true };
      
    • Configure for json instead of XML
      config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
      
    • Web.config settings
      <authentication mode="Windows"/>
      <authorization>
        <allow users="?"/>
      </authorization>
      

You can find the in-detail version here.

Some points of interest in the Web API setup:

  • In the CORS configuration we need to set which url’s are can contact our api. I’m developing an angular project with the angular-cli. The default is http://localhost:4200
var cors = new EnableCorsAttribute("http://localhost:30033,http://localhost:4200", "*", "*") { SupportsCredentials = true };
  • I’ve noticed that while setting up a project, as described in my previous post, works as expected if you start from scratch. Starting from the github repo of my sample, sometimes you keep getting a 401 unauthorized on the post request.
    If that’s the case, you can easily solve this by selecting the web api project in visual studio and open up the properties. Make sure to set Anonymous Authentication to Enabled.
    howto-ng4-winauth-01

Setting up our Angular application

I’m using the angular-cli. If you don’t know how to use it, check out their getting started information before continuing.

After setting up the application, we include the new HttpClientModule in the app.module:

import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Note: take special note of the fact that I’m using the new http import from @angular/common/http and not the old(er) one from @angular/http which still exists and works as before. I want to make use of newer functionality so I’m going for the new http implementation.

Next, add 2 services to the application.

  getUser(): Observable<string> {
    console.log('Calling getUser');
    let serviceUrl: string = `${environment.serviceBaseUrl}auth/getuser`;
    return this.http.get(serviceUrl, {responseType: 'text'})
      .map((rslt: string) =>{
        return rslt;
      });
  }
  save(data: models.IPostDataModel): Observable<string> {
    console.log('Calling getUser');
    let serviceUrl: string = `${environment.serviceBaseUrl}data/save`;

    return this.http.post(serviceUrl, data, {responseType: 'text'})
      .map((rslt: string) => {
        return rslt;
      });
  }

Note: the new http client uses json by default. And tries to parse what is returned by a request as json. In our sample web api we return a string, so we need to specify another responseType. Which in our case will be text. More about this can be found here.

Import and provide those services in the app module :

	import * as svcs from './services/';

	providers: [
	svcs.AuthenticationServiceService,
	svcs.DataServiceService
	],

Create the necessary data model for our server side PostData object.

export interface IPostDataModel {
    Id: number;
    Name: string;
    IsTrue: boolean;
    CreatedOn: Date;
}

export class PostDataModel implements IPostDataModel {

    public Id: number;
    public Name: string;
    public IsTrue: boolean;
    public CreatedOn: Date;

    constructor(obj?: IPostDataModel) {
        if (obj != null) {
            Object.assign(this, obj);
        }
    }
}

 
I’m using the AppComponent for demonstrating the windows authentication by adding 2 buttons to it.

<div style="text-align:center">
  <div class="row">
    <div class="col-lg-12">
        <button type="button" (click)="testAuthentication()">Authentication</button>
        <pre style="border:solid 1px;min-height:21px;" [ngClass]="[authBack]">{{authRslt}}</pre>
    </div>
    <div class="col-lg-12">
        <button type="button" (click)="testPostData()">Post Data</button>
        <pre style="border:solid 1px;min-height:21px;" [ngClass]="[postBack]">{{postRslt}}</pre>
    </div>
  </div>
</div>

And of course the functions that will be executed when clicking the buttons.

  testAuthentication(): void {
    this.authSvc.getUser()
      .subscribe(
        r => {this.authRslt = r; this.authBack = 'success';},
        e => {console.log(e); this.authBack = 'error';}
      );
  }

  testPostData(): void {
    this.dataSvc.save(new models.PostDataModel({Id: 1, Name: 'DeBiese', IsTrue: false, CreatedOn: new Date()}))
      .subscribe(
        r => {this.postRslt = r; this.postBack = 'success';},
        e => {console.log(e); this.postBack = 'error';}
      );
  }

HttpInterceptor

In the new http client module, intercepting a request and/or response is very easy. Writing an interceptor is like writing an angular service that implements the HttpInterceptor interface.

An example of an interceptor is the following:

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(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req);
  }
}

This interceptor is doing nothing now. It intercepts a request and passes it on without change.

In our case, we want to make sure that every request adds the current user’s windows credentials. The intercept method changes to this:

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    req = req.clone({
      withCredentials: true
    });
    return next.handle(req);
  }

The request (response) object needs to be immutable, so we need to clone the original request before we return it. We set the withCredentials parameter to true so that every outgoing request adds those credentials.

Lastly, we need to make sure that this interceptor is being used. So we need to register it in the app module.

  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: int.WinAuthInterceptor,
      multi: true
    },
    svcs.AuthenticationServiceService,
    svcs.DataServiceService
  ],

Testing the application

Let’s fire up both the Web API and the angular application:

howto-ng4-winauth-02

After clicking the buttons:

howto-ng4-winauth-03

Both requests are successful, meaning the windows authentication is working the way we want it to work.

Conclusion

Using windows authentication with the new HttpClientModule in Angular 4.3.1 (or higher) is fairly easy. Just write an interceptor and make sure it is being used by providing it in your app module.

I hope this post provides you with enough information to set this up yourself.

The full code base of this test project can be found on github.

Feel free to comment below!

Ruben Biesemans

Ruben Biesemans

Analyst Developer @ Spikes

Advertisements

11 responses to “How to implement Windows Authentication in an Angular (^4.3.1) application with a stand-alone Web API

  1. Pingback: How to implement Windows Authentication in an AngularJS application with a stand-alone Web API | Spikes Apps·

    • Well yes, I know it can be avoided. I actually had to add the “{responseType: ‘text’}” to avoid interpreting the result as json. I just usually have a .map() in non-demo situations. Force of habit I suppose.

      Like

  2. Hi I tried the demo using local IIS from vs 2015
    http://localhost/ngauth/auth/getuser: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:4200’ is therefore not allowed access. The response had HTTP status code 404.
    Any Ideas ?

    Like

    • The 404 you get indicates that your url is not correct. Try removing the “[Authorize]” attribute on the WinAuthController and see if you can access the url that way.

      Like

  3. Hi Rubens,
    this works great for passing credentials back to a web api (single hop)
    but if i want to do two hops (to a WCF service from that service) , then it needs to do kerberos instead of NTLM (it negotiates then drops back to NTLM) . Any idea of how to make this happen ? (been struggling for days) thanks

    Like

    • Hi Sean,
      I have no idea actually.
      Why are you doing the 2 hops? What’s the gain of calling an api only to have it call a wcf service?
      This is a post about using windows credentials which I would only use for an intranet web application. Can you not expose a new endpoint on the wcf service and call that directly? (Haven’t tried this either, to be honest)

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s