Tipps und Tricks für AngularJS, Teil 3: OAuth 2.0

Werkzeuge  –  0 Kommentare

Der Standard OAuth 2.0 ermöglicht die Implementierung von Single Sign-On. Durch den Einsatz von Umleitungen und die Nutzung der von UI-Router angebotenen Ereignisse können Entwickler damit zeitgemäße Log-in-Szenarien umsetzen.

Webnutzer sehen sich heutzutage mit zahlreichen Benutzerkonten konfrontiert. Das legt den Wunsch nahe, ihnen bei Bedarf die Möglichkeit anbieten zu können, sich mit nur einem Konto bei mehreren Webangeboten anzumelden. Ein Werkzeug, mit dem sich diese Aufgabe angehen lässt, ist der populäre Standard OAuth 2.0. Er lässt sich nutzen, um einer Webanwendung das Recht zu geben, im Namen des Benutzers auf Dienste zuzugreifen. Der Benutzer muss sich dafür lediglich bei einem zentralen Log-in-Provider anmelden.

Während der Artikel "Flexible und sichere Internetdienste mit OAuth 2.0" allgemeine Informationen zu OAuth 2.0 liefert, zeigt der vorliegende Beitrag, wie man den Standard in einer AngularJS-Anwendung nutzen kann. Dafür kommt die Implementierung eines sogenannten Implicit Flows zum Einsatz. Diese Spielart von OAuth 2.0 wurde speziell für JavaScript-basierte Clients geschaffen. Zur Abrundung streift der Artikel die in "OpenID Connect: Login mit OAuth, Teil 1 – Grundlagen" beschriebene OAuth-2.0-Erweiterung OpenID Connect.

Stellvertretend betrachtet

Um die Implementierung des Implicit Flow mit AngularJS zu erklären, kommt die mit Abbildung 1 veranschaulichte Demoanwendung zum Einsatz. Sie gibt dem Benutzer die Möglichkeit, einen Gutschein zu kaufen. Dazu nutzt die App eine Web API, die den Betrag des Gutscheins im Rahmen einer POST-Anfrage über einen URL-Parameter entgegennimmt und einen JSON-String mit einem Gutscheincode zurückliefert. Die Autorisierung findet mit einem OAuth-2.0-Token statt.

In der Demoanwendung kommt OAuth 2.0 für die Authentifizierung zum Einsatz (Abb. 1).

Die Demoanwendung besteht aus vier Menüpunkten, wobei hinter jedem eine mit dem UI-Router von AngularJS realisierte Route steht. Deren Konfiguration sieht wie folgt aus:

var app = angular.module("demo", ["oauth2", "ui.router"]);

app.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {

$urlRouterProvider.otherwise("/home");

$stateProvider.state('home', {
url: '/home',
templateUrl: '/app/routing-demo/home.html',
}).state('gutschein', {
url: '/gutschein',
templateUrl: '/app/routing-demo/gutschein.html',
controller: "GutscheinCtrl",
restricted: true
}).state('login', {
url: '/login?requestedUrl',
templateUrl: '/app/routing-demo/login.html',
controller: "LoginCtrl"
}).state('logout', {
url: '/logout',
templateUrl: '/app/routing-demo/logout.html',
controller: "LogoutCtrl"
});

});

Sie befindet sich in einem Modul demo, das die Module oauth2 und ui.router referenziert. Während Letzteres den UI-Router beheimatet, handelt es sich bei ersterem um ein Modul, das Teil des betrachteten Beispiels ist und wiederverwendbare Aspekte in Hinblick auf OAuth 2.0 kapselt.

Bei genauerer Betrachtung fällt die Eigenschaft restricted bei der Route gutschein auf. UI-Router kennt sie nicht und ignoriert sie deswegen. Die in weiterer Folge gezeigten Beispiele können diese Information jedoch auslesen. Sie gehen davon aus, dass Routen, bei denen diese Eigenschaft den Wert "true" aufweist, angemeldeten Benutzern vorbehalten sind.

Implicit-Flow-Implementierung mit AngularJS

Um den von OAuth 2.0 beschriebenen Implicit Flow zu implementieren, muss eine Single-Page-Anwendung eigentlich nur den Benutzer unter Verwendung der definierten Parameter auf die Log-in-Seite des Autorisierunsservers umleiten. Wenn Letzterer den Benutzer wieder zurück zur Anwendung sendet, findet diese das Access-Token im Hash-Fragment der URL.

Dreh- und Angelpunkt der betrachteten Komponente ist die Konstruktorfunktion für den vom Modul oauth2 veröffentlichten Angular-Service OAuthService, dessen Grundgerüst wie folgt aussieht:

function OAuthService($document, $window, $timeout, $q) {

this.clientId = "";
this.redirectUri = "";
this.loginUrl = "";
this.scope = "";
this.state = "";

this.createLoginUrl = function (additionalState) { };
this.tryLogin = function () { };
this.getAccessToken = function () { };
this.getIsLoggedIn = function () { };
this.logOut = function () { };

}

Die eingangs beschriebenen Eigenschaften konfigurieren den Service. Bei clientId handelt es sich um die Kennung (ID), die der Autorisierungsserver für die Anwendung vergeben hat. Die Eigenschaft redirectUri enthält die URL, an die er den Benutzer nach erfolgter Anmeldung sendet. Sie entspricht der URL der Anwendung, die beim Autorisierungsserver registriert sein muss. Als loginUrl bezeichnet der Service hingegen die URL des Servers, bei der sich der Benutzer anmeldet, und scope umfasst die von der Anwendung benötigten Rechte. Die Eigenschaft state enthält einen String, den der Client dem Autorisierungsserver sendet, der ihn dann wieder zurückschickt. Auf die Weise bleiben Zustandsinformationen trotz der vorgesehenen Umleitungen erhalten.

Neben diesen Eigenschaften findet man im OAuthService auch einige in der folgenden Tabelle beschriebenen Funktionen, deren Implementierung der Autor auf GitHub zur Verfügung stellt.

Funktion Beschreibung
createLoginUrl Die Funktion erzeugt die durch OAuth 2 beschriebene URL, die zur Log-in-Seite des Autorisierungsservers führt. Sie setzt sich aus den Werten der Eigenschaften clientId, redirectUri, loginUrl und scope zusammen. Zusätzlich enthält sie einen Parameter state, der neben den definierten Zustandsdaten eine Nonce enthält. Sie wird für spätere Überprüfungen im lokalen Speicher hinterlegt.
tryLogin
Versucht, im Hash-Fragment der aktuellen URL ein Access-Token zu finden. Sie ist aufzurufen, nachdem der Autorisierungsserver den Benutzer zur SPA zurückgesendet hat. Die Funktion prüft auch, ob die Nachricht des Autorisierungsservers den von createLoginUrl übersendeten Nonce enthält. Nur in dem Fall akzeptiert sie das Access-Token. War die Überprüfung erfolgreich, verstaut die Funktion es im lokalen Speicher und liefert true zurück.
getAccessToken Liefert das Access-Token aus dem Local Storage des Browsers.
getIsLoggedIn Informiert darüber, ob für den Benutzer ein Access-Token vorliegt.
logOut Löscht das Access-Token im Local Store des Browsers.