• 3. Juli 2016

Authentication in Angular 2 with OAuth2, OIDC and Guards for the newest new Router [English Version]

Update in January 2017: This article now uses the new library angular2-oauth2-oidc and it has been updated for Angular 2.0.

The new router for Angualr 2 provides so called Guards to influence routing. Those are services with methods that are called when the router activates or deactivates a route. The names of this methods are canActivate and canDeactivate. If such a method returns true, the router performs the current routing-action; otherwise it skips it. Furthermore, those methods can return an Observable<boolean> to postpone this decision.

In in my post here I've showed with an example how to use canDeactivate. It displays a warning when the user tries to exit a route and gives them the option to decide to stay.

This post shows how an application can use canActivate to keep an unauthenticated or unauthorized user away from specific routes. This isn't really about security, cause in browser-based applications security has to be implemented at server-side. Rather it's about usability cause it gives the application the possibility to ask the user to login in such cases. The whole source code of this sample can be found here. Besides Guards it also uses the security standards OAuth 2 and OpenId Connect (OIDC) to decouple the authentication and authorization from the application.

Preperation

To use OAuth 2 and OIDC, the here described sample uses my implementation, which can be installed via npm:

npm install angular-oauth2-oidc --save

After downloading this library, the application has to import the OAuthModule in the AppModule:

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

[...]

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

The top level component can be used to configure the OAuthServices. The settings in the next sample point to an OAuth2/OIDC Auth Server that is available online an can be used 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.html";

    // 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 method tryLogin checks, whether the app has got security tokens via the hash-fragment of the URL. It parses those tokens and extracts information of the current user.

In cases where this information is used for security-relevant procedures, the app has to validate the token. This is especially the case in hybrid and native apps that use it to access local resources. The following sample uses a callback to validate the token. For this, it calls a web api, that checks its 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();
    }
});

Login

To redirect the user to the login-form of the Auth Server, the app has only to call the method initImplicitFlow that is provided by the OAuthService.

The method login in the following sample shows this. The method logout logs off the current user. For this purpose it deletes the saved tokens. If the service has been initialized with a logout url, it also redirects the user to this URL:

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 to that, the getter userName tries to find out the user's first-name. For this, it accesses the claims that the library found within the security-token. The template of this component binds to those 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>

Keeping away unauthorized users with guards

To keep unauthorized users away from some routes the application can use guards. The following sample shows an implementation for this. Its just an Angular-2-Service that implements CanActivate and receives the OAuthService by the means of dependency injection.

The interface defines a method canActivate. The presented implementation checks, whether there are the necessary security tokens. Those are an Access-Token (OAuth2) as well as an Id-Token (OpenId Connect). If there are both, it returns true to signal the router that the component in question can be activated. Otherwise it skips the current routing-action by returning false:

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 of canActivate inform about the current route as well as about the requested route.

In addition to that, the guard has to be registered with the property canActivate in the routing-configuration. This property does not directly point to the guards to use but to tokens that can be used to request 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 the current case, the guard is used as its token as well. Therefore, it can be directly mentioned within the provider configuration.

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

[...]

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

Calling a web api

To call a web api, the application has to pass the access-token. It can retrieve it from the method getAccessToken of the OAuthService. Usually, this Token has to be transmitted via the HTTP header Authorization:

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 specifies that the passed value is a so called bearer token. This is a token that gives the bearer - here the SPA - the rights that are associated with it.

 

 
Hier können Sie eine Anfrage für eine unverbindliche Schulung ode Beratung bzw. einen Workshop erstellen.
 
Unverbindliche Anfrage
 
 

Schulungen

Angular Schulung: Strukturierte Einführung

Lernen Sie in dieser interaktiven Schulung anhand einer Beispielanwendung den Einsatz von Angular für Ihre erfolgreichen Projekte kennen. Sie durchdringen die Hintergründe und bauen von der ersten Minute an auf Best Practices auf.

Details

Advanced Angular: Enterprise-Anwendungen und Architektur

In dieser Schulung erfahren Sie alles für die Entwicklung großer Anwendungen mit Angular: Mono-Repos, Micro-Apps, State Management, Performance und mehr

Details

Angular: Strukturierte Einführung

Seit der Ankündigung von Angular (2+) fragen sich Entwicklungs-Teams, welche Migrationspfade für AngularJS-1.x-Anwendungen vorliegen werden. Das im Lieferumfang von Angular enthaltene ngUpgrade bietet eine Antwort darauf. Es erlaubt einen Parallelbetrieb von AngularJS 1.x und Angular (2+) und stellt somit die Grundlage für eine schrittweise Migration dar.

Details

Progressive Web-Apps mit Angular

Progressive Web Apps bieten den Komfort nativer Anwendungen, indem sie auf moderne Browser APIs, wie Service Worker, setzen. Sie sind installierbar sowie offlinefähig und nutzen Hintergrundprozesse für Datensynchronisation und Push-Notifications. Diese Schulung zeigt anhand eines durchgehenden Beispiels was sich genau hinter diesem neuen Konzept verbirgt, wie solche Anwendungen mit Angular entwickelt werden und wie Sie in Ihren Projekten von den dahinterstehenden Ideen profitieren.

Details

Reaktive Architekturen mit Angular und Redux

Dieses interaktive Seminar vermittelt, wie Sie reaktive Anwendungen mit Angular entwickeln können.

Details

TypeScript

TypeScript gibt Ihnen alle Möglichkeiten der neuen JavaScript-Standards und zusätzlich ein statisches Typsystem, das dabei hilft, Fehler möglichst früh zu erkennen. Außerdem ist TypeScript die Grundlage für Angular. In diesem interaktiven Seminar lernen Sie diese mächtige Sprache anhand einer Fallstudie kennen.

Details

Weitere Schulungen ...