January 2017 update: This post has been brought in line with the final Angular 2.x release and now relies on the angular-oauth2-oidc library.

Angular 2's new router empowers single-page applications to shape navigation behavior through what are known as guards. These are services whose methods get invoked by the router when routes are activated or deactivated. Those methods carry the fitting names canActivate and canDeactivate. By returning a boolean, they signal whether the requested transition should be permitted. They can also return an Observable<boolean> to defer that verdict. When the router receives true through that Observable, it proceeds with the routing operation; a false value causes it to abort.

In an earlier post, I walked through a demonstration of canDeactivate. That example displayed a warning before leaving a route, offering the user a chance to change their mind.

The current post focuses on using canActivate to shut out unauthorized users from selected routes. This is not primarily about security — after all, for browser-based SPAs real protection must live in the backend. Rather, it improves user experience by letting the app prompt the user to log in when needed. The complete source code for the example discussed here is available online. Beyond the guard concept, it employs the security standards OAuth 2 and OpenID Connect (OIDC) to detach authentication and authorization from the application itself, which also enables single sign-on.

The first ingredient is a library that implements OAuth 2 and OIDC. The example at hand uses my own implementation, which is published on npm:

npm install angular-oauth2-oidc --save

Next, the OAuthModule provided by this library has to be imported into the root module:

import { OAuthModule } from 'angular-oauth2-oidc';

[...]

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    OAuthModule.forRoot()
    [...]
  ],
  [...]
})
export class AppModule {
}

A natural spot for configuring the library is the constructor of the top-level component. The settings shown below point to an OAuth2/OIDC auth server that I host in the cloud for testing purposes:

import {Component} from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';

@Component({
  selector: 'app', // <app></app>
  templateUrl: './app.component.html',
})
export class AppComponent {

  constructor(private oauthService: OAuthService) {

    // URL of the SPA to redirect the user to after login
    this.oauthService.redirectUri = window.location.origin + "/index/";

    // The SPA's id. The SPA is registerd with this id at the auth-server
    this.oauthService.clientId = "spa-demo";

    // set the scope for the permissions the client should request
    // The first three are defined by OIDC. The 4th is a usecase-specific one
    this.oauthService.scope = "openid profile email voucher";

    // set to true, to receive also an id_token via OpenId Connect (OIDC) in addition to the
    // OAuth2-based access_token
    this.oauthService.oidc = true; // ID_Token

    // Use setStorage to use sessionStorage or another implementation of the TS-type Storage
    // instead of localStorage
    this.oauthService.setStorage(sessionStorage);

    // Discovery Document of your AuthServer as defined by OIDC
    let url = 'https://steyer-identity-server.azurewebsites.net/identity/.well-known/openid-configuration';

    // Load Discovery Document and then try to login the user
    this.oauthService.loadDiscoveryDocument(url).then(() => {

      // This method just tries to parse the token(s) within the url when
      // the auth-server redirects the user back to the web-app
      // It dosn't send the user the the login page
      this.oauthService.tryLogin({});

    });

  }

}

Calling tryLogin checks whether the app has received security tokens within the hash fragment. It parses those tokens and extracts user information from them. If that information is security-sensitive, the app must validate the token first. This matters especially for hybrid and native apps that rely on this data to access local resources. In the following example, a callback is used to hand validation off to the backend:

this.oauthService.tryLogin({
    validationHandler: context => {
        var search = new URLSearchParams();
        search.set('token', context.idToken); 
        search.set('client_id', oauthService.clientId);
        return http.get(validationUrl, { search }).toPromise();
    }
});

Sending the user off to the auth server's login page is as simple as invoking initImplicitFlow on the OAuthService. The code below demonstrates this within a login method. The logout method, meanwhile, signs the user out: it clears all security tokens and, when a logout URL has been configured, redirects the user there:

import { Component } from '@angular/core';
import { OAuthService} from 'angular2-oauth2/oauth-service';

@Component({
    selector: 'home',
    template: require('./home.component.html')
})
export class HomeComponent {

    constructor(private oauthService: OAuthService) {
    }

    public login() {
        this.oauthService.initImplicitFlow();
    }

    public logout() {
        this.oauthService.logOut();
    }

    public get userName() {

        var claims = this.oauthService.getIdentityClaims();
        if (!claims) return null;

        return claims.given_name;
    }

}

In addition, the userName getter attempts to retrieve the user's first name. It pulls this value from the claims that the library has parsed out of the security token.

The corresponding template binds to these properties and methods:

<h1 *ngIf="!userName">Welcome!</h1>
<h1 *ngIf="userName">Hello, {{userName}}!</h1>
<p>Welcome to this demo-application.</p>
<p>
    <button (click)="login()" class="btn btn-default">Login</button>
    <button (click)="logout()" class="btn btn-default">Logout</button>
</p>    
<p>
    Username/Passwort: max/geheim
</p>

Guards are a clean way to block anonymous or improperly authenticated users from certain routes. The implementation below shows how this works. It is an Angular 2 service that implements CanActivate and gets the OAuthService injected into it. The canActivate method prescribed by that interface checks whether the required security tokens are present: the access token defined by OAuth 2 and the ID token added by OIDC. When both exist and neither has expired, it returns true, thereby giving the router the green light to carry out the requested action. In any other case it returns false, which halts the routing operation:

import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { OAuthService } from 'angular-oauth2-oidc';
import { Injectable } from '@angular/core';

@Injectable()
export class FlightBookingGuard implements CanActivate {

    constructor(private oauthService: OAuthService) {
    }

    canActivate(
        route: ActivatedRouteSnapshot, 
        state: RouterStateSnapshot) {

            var hasIdToken = this.oauthService.hasValidIdToken();
            var hasAccessToken = this.oauthService.hasValidAccessToken();

            return (hasIdToken && hasAccessToken);
    }
}

The parameters received by canActivate report on the current route as well as the intended routing action.

The guard then still needs to be registered in the routing configuration, inside the canActivate property of the respective routes. At that point it is only a token, to be bound to an actual service later on via a provider:

import { RouterConfig, provideRouter } from '@angular/router';
import { HomeComponent} from './home/home.component';
import { FlightSearchComponent} from './flight-search/flight-search.component';
import { PassengerSearchComponent} from './passenger-search/passenger-search.component';
import { FlightEditComponent} from './flight-edit/flight-edit.component';
import { FlightBookingComponent} from './flight-booking/flight-booking.component';
import { FlightBookingGuard} from './flight-booking/flight-booking.guard';
import { FlightEditGuard} from './flight-edit/flight-edit.guard';
import { InfoComponent} from './info/info.component';
import { DashboardComponent} from './dashboard/dashboard.component';

const APP_ROUTES: RouterConfig = [
    {
        path: '/home',
        component: HomeComponent,
        index: true
    },
    {
        path: '/info',
        component: InfoComponent,
        outlet: 'aux'

    },
     {
        path: '/dashboard',
        component: DashboardComponent,
        outlet: 'aux'
    },    
    {
        path: '/flight-booking',
        component: FlightBookingComponent,
        canActivate: [FlightBookingGuard],
        children: [
            {
                path: '/flight-search',
                component: FlightSearchComponent
            },
            {
                path: '/passenger-search',
                component: PassengerSearchComponent
            },
            {
                path: '/flight-edit/:id',
                component: FlightEditComponent
            }
        ]
    }
];

In the present case the token and the service to be injected are one and the same, so merely adding the service to the provider configuration suffices:

import { OAuthModule } from 'angular-oauth2-oidc';

[...]

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    OAuthModule.forRoot()
    [...]
  ],
  providers: [
    FlightBookingGuard
  ]
  [...]
})
export class AppModule {
}

Calling a Web API

When invoking Web APIs, the access token has to be included with the request. The OAuthService exposes this token through its getAccessToken method. It is sent along via the Accept header:

public find(from: string, to: string) {
    var url = this.baseUrl + "/api/flight";

    var search = new URLSearchParams();
    search.set('from', from);
    search.set('to', to);

    var headers = new Headers();
    headers.set('Accept', 'text/json');
    headers.set('Authorization', 'Bearer ' + this.oauthService.getAccessToken())

    return new Observable((observer: Observer<Flight[]>) => {
        this.http
            .get(url, { search, headers })
            .map(resp => resp.json())
            .subscribe((flights) => {
                this.flights = flights;
                observer.next(flights);
            });
    });
}

The value Bearer signals that the transmitted value is a bearer token. Such tokens grant whoever presents them — here, the SPA — the rights attached to them.