Updated January 2017: This article reflects the new angular2-oauth2-oidc library and has been revised for Angular 2.0.

Angular 2's new router exposes hooks called Guards that let you control navigation. These are services whose methods get invoked when a route is about to be activated or deactivated. The relevant method names are canActivate and canDeactivate. When such a method yields true, the router proceeds with the current navigation; otherwise, it aborts it. These methods can also return an Observable<boolean> if the decision needs to be deferred.

In a previous post I demonstrated how to leverage canDeactivate. That example pops up a warning when a user attempts to leave a route, letting them choose whether to remain.

This article focuses on using canActivate to prevent unauthenticated or unauthorized users from accessing specific routes. This is less about hard security—in browser-based apps, real security must live on the server—and more about user experience, since it lets the application prompt the user to log in when necessary. The complete sample source code is available here. Alongside Guards, it relies on the OAuth 2 and OpenID Connect (OIDC) standards so that authentication and authorization stay decoupled from the app itself.

Getting Started

For OAuth 2 and OIDC support, this sample depends on a library I published, installable through npm:

npm install angular-oauth2-oidc --save

Once the package is in place, the application must import the OAuthModule inside the AppModule:

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

[...]

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

The root component is a good spot for configuring the OAuthServices. The configuration below points to an OAuth2/OIDC authorization server that is publicly accessible and suitable for testing:

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({});

    });

  }

}

The tryLogin method inspects the URL's hash fragment to determine whether security tokens were returned. If found, it parses them and pulls out the current user's details.

When that user information matters for security-sensitive operations, the token needs to be verified. This is especially important in hybrid or native apps that use the token to reach local resources. The example below uses a callback for token validation, invoking a web API that checks the token's signature:

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

Signing In

To send the user over to the authorization server's login page, the application simply invokes initImplicitFlow on the OAuthService.

In the next snippet, the login method demonstrates this. The logout method signs the current user out by clearing stored tokens. If a logout URL was supplied during setup, the service also 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;
    }

}

Additionally, the userName getter attempts to recover the user's first name by reading claims embedded in the security token. The component template binds directly to these properties:

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

Blocking Unauthorized Users with Guards

Guards are the mechanism an application can use to shield certain routes from unauthorized users. The implementation below is straightforward: it's an Angular 2 service that implements CanActivate and obtains the OAuthService through dependency injection.

The interface mandates a canActivate method. The version shown here verifies that the required security tokens exist—namely, an Access Token (from OAuth2) and an Id Token (from OpenID Connect). If both are present, it returns true, telling the router the target component may be activated. Otherwise, it returns false to halt the current navigation:

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 arguments passed to canActivate provide details about both the current and the requested route.

Beyond that, the guard must be wired into the routing configuration using the canActivate property. This property doesn't reference the guard instances directly; instead, it lists tokens that can be resolved to the guards via DI:

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 this particular setup, the guard itself serves as its own token, so it can be listed directly in the providers configuration.

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

[...]

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

Calling a Web API

To talk to a web API, the application must forward its access token. This is obtained through the getAccessToken method on the OAuthService. Typically, the token goes into the Authorization HTTP 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 Bearer prefix signals that the following value is a bearer token—meaning whoever holds it, in this case the SPA, inherits the permissions attached to it.