Blog

Sicherheit in einer Angular-Anwendung

9 Apr 2024

Dieser Artikel beleuchtet die Grundlagen und Best Practices für die Integration von Authentifizierungs- und Autorisierungsmechanismen in Angular-Anwendungen. Wir werden uns clientseitige Ansätze ansehen, die zur Absicherung von Angular-basierten Projekten beitragen, sowie gängige Herausforderungen und Lösungen in diesem Bereich diskutieren. Mit einem Fokus auf aktuelle Technologien und Methoden bietet dieser Artikel wertvolle Einblicke und Anleitungen für Entwickler:innen, die ihre Angular-Anwendungen sicherer und widerstandsfähiger gegenüber Bedrohungen machen wollen.

Authentifizierung (AuthN) und Autorisierung (AuthZ) sind zwei entscheidende Aspekte der Sicherheit in modernen Webanwendungen. In der Welt von Angular, einem populären Framework für das Erstellen von Single Page Applications (SPAs), ist die Implementierung robuster AuthN- und AuthZ-Strategien unerlässlich, um die Sicherheit der Anwendung und der darin verarbeiteten Daten zu gewährleisten.

Bei der Diskussion um Sicherheit in Webanwendungen rückt unweigerlich die Frage in den Fokus, was genau es zu schützen gilt. In diesem Kontext bietet die sogenannte CIA-Triade [1] einen klaren Rahmen, indem sie drei essenzielle Sicherheitsziele definiert – Vertraulichkeit, Integrität und Verfügbarkeit (Abb. 1).

kraus_authn_authz_1.tif_fmt1.jpgAbb. 1: Die CIA-Triade
  1. Vertraulichkeit (Confidentiality): Dieses Ziel bezieht sich auf den Schutz von Daten vor unbefugtem Zugriff oder Offenlegung. Es gewährleistet, dass sensible Informationen nur für diejenigen zugänglich sind, die berechtigt sind. Maßnahmen zur Aufrechterhaltung der Vertraulichkeit umfassen Verschlüsselung, Zugriffskontrollen und strenge Authentifizierungsverfahren.

  2. Integrität (Integrity): Integrität bezieht sich auf die Bewahrung der Genauigkeit und Vollständigkeit von Daten. Es geht darum, sicherzustellen, dass Informationen nicht ohne Autorisierung verändert, manipuliert oder auf andere Weise beschädigt werden. Methoden zur Sicherung der Integrität umfassen Hashfunktionen, digitale Signaturen und Redundanzmechanismen.

  3. Verfügbarkeit (Availability): Verfügbarkeit bedeutet, dass Informationen und Ressourcen im Bedarfsfall zugänglich und nutzbar sind. Dieses Ziel soll sicherstellen, dass Systeme und Daten trotz verschiedener Bedrohungen wie Hardwareausfällen, Softwareproblemen oder Cyberangriffen zugänglich bleiben. Maßnahmen zur Gewährleistung der Verfügbarkeit sind unter anderem Back-up-Systeme, Redundanzen und effiziente Wartungsprozesse.

Die CIA-Triade dient als Grundlage für das Verständnis und die Bewertung von Sicherheitsmaßnahmen in IT-Systemen. Mit der Integration von Authentifizierung und Autorisierung sorgen wir also für einen Schutz des Sicherheitsziels der Vertraulichkeit: Schutz vor unbefugtem Zugriff.

Authentifizierung und Autorisierung

Authentifizierung und Autorisierung sind zwei grundlegende Sicherheitskonzepte, die vor allem in der Welt der Informationstechnologie Verwendung finden. Immer wieder werden beide Konzepte vermischt oder sogar synonym verwendet, daher möchte ich mit einer kleinen Definition beide Sicherheitskonzepte noch einmal voneinander abtrennen:

Authentifizierung bezieht sich hierbei auf den Prozess, mit dem überprüft wird, ob jemand oder etwas tatsächlich das ist, was es vorgibt zu sein. Im Kontext von Webanwendungen bedeutet das in der Regel die Überprüfung der Identität eines Benutzers, typischerweise durch Benutzername und Passwort, biometrische Daten, Einmal-Passwörter oder andere Methoden. Authentifizierung ist der erste Schritt, um zu gewährleisten, dass der Zugriff auf ein System oder eine Anwendung von einer legitimen Quelle aus erfolgt. Oftmals werden bei der Authentifizierung sogenannte zweite Faktoren (Multi-factor Authentication) genutzt, um eine weitere Sicherheitsstufe zu integrieren. Das kann in Form eines SMS-Codes oder über eine speziell dafür ausgelegte Applikation [2] auf dem Smartphone integriert werden.

Autorisierung hingegen ist der Prozess der Entscheidung, ob ein authentifizierter Benutzer Zugriff auf bestimmte Ressourcen oder Funktionen erhalten soll. Das beinhaltet in der Regel die Überprüfung von Benutzerrechten oder Rollen gegenüber den Zugriffsanforderungen. Autorisierung erfolgt nach der Authentifizierung und bestimmt, was ein Benutzer in einem System oder einer Anwendung tun darf.

Diese beiden Konzepte sind eng miteinander verknüpft, aber dennoch unterschiedlich:

  • Authentifizierung stellt fest, wer der Benutzer ist.

  • Autorisierung bestimmt, was der Benutzer tun darf.

Die Herausforderung besteht darin, ein Gleichgewicht zu finden zwischen dem Schutz sensibler Daten und Ressourcen und der Bereitstellung eines nahtlosen und benutzerfreundlichen Erlebnisses für legitime Benutzer. So ist beispielsweise eine Ressource optimal geschützt, wenn der Benutzer noch vier oder mehr weitere Faktoren zur Authentifizierung angeben muss; der Benutzer selbst ist aber vermutlich schnell genervt von dem bereitgestellten Authentifizierungsprozess.

BRINGEN SIE LICHT INS ANGULAR-DUNKEL

Die ersten Schritte in Angular geht man am besten im Basic Camp.
→ Nächster Termin: 9. - 11. Dezember, online

Authentifizierung in einer Webapplikation

Bei der Integration eines Authentifizierungsprozesses in eine Webanwendung stehen uns im Wesentlichen zwei etablierte Methoden zur Verfügung:

  • cookiebasierte Authentifizierung

  • tokenbasierte Authentifizierung

Beide Ansätze finden heutzutage große Anwendung und bringen jeweils ihre spezifischen Vorzüge und Herausforderungen mit sich. Im Folgenden werde ich diese beiden Konzepte detailliert erörtern und die potenziellen Vor- und Nachteile jedes Ansatzes beleuchten, um ein tieferes Verständnis ihrer Anwendung und Wirksamkeit in modernen Webanwendungen zu vermitteln.

Cookiebasierte Authentifizierung

Cookiebasierte Authentifizierung ist ein verbreiteter Ansatz zur Verwaltung von Benutzersitzungen in Webanwendungen. Dabei wird nach erfolgreicher Authentifizierung des Nutzers ein kleines Stück Daten – bekannt als Cookie – vom Server an den Webbrowser des Benutzers gesendet und dort gespeichert. Dieses Cookie wird dann bei jeder folgenden Anfrage des Browsers an den Server zurückgesendet, wodurch der Server den Benutzer und dessen Sitzungszustand über verschiedene Anfragen hinweg verfolgen kann. Dabei werden folgende Schritte durchgeführt:

  • Anmeldung: Der Benutzer gibt seine Anmeldeinformationen (wie Benutzername und Passwort) ein. Nach erfolgreicher Überprüfung dieser Informationen durch den Server wird ein Sitzungscookie erstellt (Abb. 2).

  • Sitzungscookie: Dieses Cookie enthält in der Regel eine einzigartige Sitzungs-ID, die den Benutzer identifiziert. Es speichert keine sensiblen Benutzerdaten direkt, da Cookiedaten innerhalb eines Webbrowsers gespeichert werden und diese theoretisch jederzeit einsehbar sind (Abb. 3).

  • Sicherheit: Um die Sicherheit zu erhöhen, können Cookies mit Attributen wie httpOnly (verhindert den Zugriff durch Client-side-Skripte) und Secure (sorgt dafür, dass Cookies nur über HTTPS gesendet werden) versehen werden [3].

  • Sitzungsverwaltung: Bei jeder Anfrage des Benutzers an den Server wird das Sitzungscookie mitgesendet. Der Server prüft die Gültigkeit des Cookies und erlaubt den Zugriff auf geschützte Ressourcen, wenn das Cookie gültig ist (Abb. 4).

  • Ablauf und Abmeldung: Cookies haben ein Ablaufdatum. Nach dem Ablauf oder wenn der Benutzer sich explizit abmeldet, wird das Cookie entweder vom Server als ungültig markiert oder vom Browser gelöscht.

Die cookiebasierte Authentifizierung ist besonders effektiv für traditionelle Webanwendungen, bei denen der Server eine aktive Rolle bei der Sitzungsverwaltung spielt. Ein weiterer wichtiger Faktor ist das Management der Cookies über den Browser: Entwickler:innen müssen hierbei keinen eigenen Code für das Verwalten der Cookies schreiben. Das verringert die Komplexität der Webanwendung, da wir uns darauf verlassen können, dass der Webbrowser bei jeder Anfrage an unser Backend die Cookiedaten automatisch mitsendet.

kraus_authn_authz_2.tif_fmt1.jpgAbb. 2: Nach dem Log-in wird ein Sitzungscookie erstellt
kraus_authn_authz_3.tif_fmt1.jpgAbb. 3: Das Cookie wird im Browser gespeichert
kraus_authn_authz_4.tif_fmt1.jpgAbb. 4: Der Server prüft die Gültigkeit des gespeicherten Cookies

Durch den Einsatz der bereits erwähnten Cookieattribute secure und httpOnly erhöhen wir die Sicherheit in der Verwaltung unserer Cookies. Insbesondere bietet das Setzen des httpOnly-Attributs einen wirksamen Schutz gegen Manipulationen des Cookies durch bösartige JavaScript-Einschleusungen, bekannt als Cross-site-Scripting-(XSS-)Attacken. Dieses Attribut bewirkt, dass die Cookies nicht mehr über das Browser-API document.cookie zugänglich sind, wodurch ein zusätzliches Sicherheitsniveau im Umgang mit sensiblen Benutzerdaten etabliert wird.

Auch wenn die Nutzung von Cookies bösartige Manipulationen über JavaScript verringern kann, sind sie leider die Hauptursache für sogenannte Cross-site-Request-Forgery-(CSRF-)Attacken.

Eine CSRF-Attacke, auch als „One Click Attack“ oder „Session Riding“ bekannt, ist eine Art von Cyberangriff, bei dem ein Angreifer Nutzer dazu bringt, ungewollte Aktionen auf einer Webseite auszuführen, auf der sie gerade angemeldet sind. Das geschieht typischerweise, ohne dass der Nutzer sich dessen bewusst ist. Hierbei wird genau die Tatsache ausgenutzt, dass das Opfer bereits auf der Zielwebseite über ein Cookie authentifiziert ist und dieses Cookie bei allen Anfragen auf die Zielwebseite automatisch vom Webbrowser mitgesendet wird. Die Anfrage kann daraufhin verschiedene Aktionen auslösen, wie das Ändern von Kontoinformationen, das Versenden von Nachrichten oder das Durchführen von Transaktionen. Die Open-Web-Application-Security-Project-(OWASP-)Organisation hat einige Tipps und Tricks in einem Cheat Sheet [4] zusammengestellt, wie man sich am besten vor CSRF-Attacken schützen kann.

Neben der Einführung von potenziellen CSRF-Attacken haben Cookies noch einen weiteren Nachteil: Sie sind weniger geeignet für moderne, verteile Architekturen wie Single Page Applications (SPAs) und Microservices, in denen oft tokenbasierte Authentifizierungsmethoden, wie z. B. JSON Web Tokens (JWT), bevorzugt werden. In Anbetracht dessen möchten wir uns nun der tokenbasierten Authentifizierung zuwenden und diese eingehend beleuchten.

Newsletter

Jetzt anmelden & regelmäßig wertvolle Insights in Angular sowie aktuelle Weiterbildungen erhalten!

Tokenbasierte Authentifizierung

Tokenbasierte Authentifizierung ist ein Verfahren, bei dem statt traditioneller Session-Cookies ein Authentifizierungstoken für die Identifizierung und Verwaltung von Benutzersitzungen verwendet wird. Dieses Verfahren wird wie bereits erwähnt häufig in modernen Webanwendungen und insbesondere in API-basierten Diensten und Single-Page-Anwendungen eingesetzt. Folgendes sind die Kernaspekte der tokenbasierten Authentifizierung:

  • Authentifizierung und Tokenerstellung: Der Benutzer gibt seine Anmeldeinformationen (Benutzername und Passwort) ein. Nach erfolgreicher Überprüfung erstellt der Server ein Token, oft ein JSON Web Token (JWT), das Informationen über den Benutzer und die Sitzung enthält. Meist kommt hierbei ein sogenannter externer Identity Provider ins Spiel der die sichere Generierung und Benutzerverwaltung übernimmt (Abb. 5).

  • Tokenversendung: Das Token wird an den Client (den Browser oder die Anwendung des Benutzers) gesendet und dort gespeichert, beispielsweise im Local Storage, in einer Session oder in einem Cookie (Abb. 6).

  • Clientseitige Verwendung des Tokens: Bei jeder folgenden Anfrage an den Server fügt der Client das Token im HTTP-Header hinzu. Dies dient als Nachweis der Authentifizierung und Autorisierung für die Anfrage (Abb. 7).

  • Serverseitige Validierung: Der Server validiert das Token bei jeder Anfrage, um sicherzustellen, dass es gültig und nicht manipuliert wurde. Nach erfolgreicher Validierung gestattet der Server den Zugriff auf geschützte Ressourcen.

  • Ablauf und Erneuerung: Token haben in der Regel ein Ablaufdatum. Nach Ablauf kann der Benutzer entweder erneut seine Anmeldeinformationen eingeben oder ein Refresh-Token verwenden, um ein neues Zugriffstoken zu erhalten.

Diese Methode ist besonders effektiv für Anwendungen, die Ressourcen über mehrere Server oder Dienste verteilen, da das Token leicht zwischen verschiedenen Systemkomponenten übertragen werden kann, ohne die Notwendigkeit einer zentralen Sitzungsverwaltung.

kraus_authn_authz_5.tif_fmt1.jpg
Abb. 5: Bei der tokenbasierten Authentifizierung ist meist ein Identity Provider im Spiel
kraus_authn_authz_6.tif_fmt1.jpg
Abb. 6: Das Token wird an den Client gesendet
kraus_authn_authz_7.tif_fmt1.jpg
Abb. 7: Das Token im Header dient als Authentifizierungsnachweis

Token können auf verschiedene Arten in Browsern oder Webanwendungen gespeichert werden. Doch auch hierbei gibt es einiges zu beachten: Wenn Sie den lokalen Speicher eines Browsers verwenden, kann eine Subdomain nicht auf Tokens zugreifen. Sie können jedoch von jedem JavaScript-Code auf der Webseite sowie von Browser-Plug-ins aufgerufen und manipuliert werden. Das ist also keine empfohlene Methode – sie stellt zum einen ein Sicherheitsrisiko dar und darüber hinaus müssen Sie den Speicher selbst verwalten.

Die Verwaltung der Tokens erfordert oftmals viel manuelle Arbeit: Ein Token kann beispielsweise nicht widerrufen werden. Selbst wenn ein Token gestohlen wird, bleibt es gültig, bis es abläuft, was zu einer schwerwiegenden Sicherheitslücke führt. Um dieses Problem zu umgehen, müssen Sie eine Sperrlistentechnik implementieren, die eine komplexere Einrichtung erfordert.

Aber auch ohne an bösartige Attacken zu denken, kann eine eigenständige Verwaltung der Tokens kompliziert sein: Die Informationen in einem Token stellen eine Momentaufnahme zum Zeitpunkt der ursprünglichen Erstellung des Tokens dar. Der zugehörige Benutzer verfügt möglicherweise nun über andere Zugriffsebenen oder wurde vollständig aus dem System entfernt.

Angesichts der Komplexität und Bedeutung einer sicheren Tokengenerierung und -verwaltung empfiehlt es sich fast immer, diese Aufgaben nicht eigenständig zu übernehmen. Oft ist es vorteilhafter und sicherer, externe Identity-as-a-Service-Anbieter (sogenannte Identity Provider wie Azure Active Directory [5] oder Auth0 [6] by Okta) zu integrieren oder etablierte Identity- und Access-Management-Tools zu verwenden, wie sie beispielsweise mit Keycloak [7] von Red Hat angeboten werden.

Diese Lösungen bieten nicht nur eine robuste Verwaltung von Nutzerkonten und Tokens, sondern erleichtern durch bereitgestellte Entwicklerbibliotheken auch signifikant die Integration in Webanwendungen. Dadurch wird nicht nur die Sicherheit erhöht, sondern auch der Entwicklungsprozess effizienter und benutzerfreundlicher gestaltet.

Wie das Ganze nun konkret aussehen kann, möchte ich Ihnen anhand einer beispielhaften Implementierung demonstrieren. Hierbei werde ich den Identity Provider Auth0 by Okta nutzen, um Authentifizierung und Autorisierung in eine bestehende Angular-Applikation zu integrieren.

Bevor wir mit der konkreten Implementierung starten können, müssen wir uns zuvor kurz mit den offenen Standards für Autorisierung und Authentifizierung auseinandersetzen. Diese beschreiben verschiedene Workflows und Richtlinien, wie eine Webanwendung ein Token sicher erhalten kann, und bieten eine Spezifikation für weitere Authentifizierungskonzepte wie Single Sign-on (SSO) oder die Integration sozialer Medien (Social Log-in).

OAuth und OpenID Connect

OpenID Connect ist eine Identitätsverwaltungsschicht, die auf dem OAuth-Protokoll aufbaut. Sie ermöglicht es Clients, die Identität eines Endbenutzers zu verifizieren und grundlegende Profilinformationen über den Benutzer zu erhalten. OpenID Connect wird oft für Single-Sign-on-(SSO-)Lösungen verwendet und ist weit verbreitet in modernen Webanwendungen und Mobile-Apps. OpenID Connect erweitert hierbei OAuth um die Einführung von ID-Tokens. Diese Tokens sind im JWT-Format und enthalten Informationen über die Authentifizierung des Benutzers.

OAuth ist ein offener Standard für Zugriffsdelegation, der es Benutzern ermöglicht, Dritten eingeschränkten Zugriff auf ihre Ressourcen auf einem anderen Server zu gewähren, ohne dabei ihre Zugangsdaten preiszugeben. Ursprünglich für die API-Autorisierung entwickelt, hat sich OAuth zu einem Schlüsselstandard in der modernen Web- und Anwendungsentwicklung entwickelt. OAuth ermöglicht es Anwendungen, im Namen des Benutzers auf Ressourcen zuzugreifen, indem sie ein Zugriffstoken verwenden, das vom Ressourcenbesitzer (dem Benutzer) genehmigt wurde. Das bedeutet, dass Anwendungen keine Benutzernamen und Passwörter speichern müssen. Kurz gesagt ist OAuth eine Spezifikation für die Autorisierung eines Benutzers: Sie können spezifische Berechtigungen (Scopes) an die Anwendungen vergeben, was eine fein abgestimmte Kontrolle darüber ermöglicht, auf welche Informationen und Funktionen die Anwendung zugreifen darf.

OAuth definiert verschiedene Flows (auch als Grant Types bezeichnet), um unterschiedliche Anwendungsfälle und Szenarien für die Authentifizierung und Autorisierung zu unterstützen. Jeder dieser Flows beschreibt einen spezifischen Prozess zur Erlangung eines Zugriffstokens – dem sogenannten Access-Token.

Je nach Anwendung werden verschiedene Flows empfohlen. Die aktuelle Version OAuth 2.1 [8] spezifiziert hierbei hauptsächlich drei Flows:

  • Client Credentials Flow

  • Device Code Flow

  • Authorization Code Flow mit Proof-Key-Code-Exchange-(PKCE-)Erweiterung

Der Client Credentials Flow wird immer dann verwendet, wenn der Zugriff zwischen zwei Anwendungen ohne Benutzerinteraktion erfolgt. Die Anwendung authentifiziert sich mit ihren eigenen Credentials (nicht mit Benutzer-Credentials) beim OAuth-Server und erhält ein Zugriffstoken. Der Device Code Flow wird für Geräte genutzt, die keine einfache Möglichkeit bieten, Text einzugeben (wie Smart-TVs oder Spielkonsolen). Es verwendet ein Gerät, das einen Code anzeigt, den der Benutzer auf einem anderen Gerät (z. B. einem Smartphone) eingibt, um die Authentifizierung zu bestätigen.

Wenn wir mit Angular eine SPA entwickeln, wird empfohlen, dass wir den Authorization Code Flow mit PKCE nutzen. Dieser Flow ist für Anwendungen gedacht, die auf einem Server laufen. Bei einem einfachen Authorization Code Flow (ohne PKCE-Erweiterung) authentifiziert sich der Benutzer zunächst bei seinem Identity Provider und erteilt der Anwendung die Berechtigung (Abb. 8). Daraufhin erhält die Anwendung einen Autorisierungscode, den sie gegen ein Zugriffstoken eintauschen kann (Abb. 9). Dieser Flow gilt als einer der sichersten, da die Nutzer-Credentials zu keinem Zeitpunkt in unserer Frontend-Applikation einsehbar sind.

kraus_authn_authz_8.tif_fmt1.jpg
Abb. 8: Authentifizierung beim Identity Provider
kraus_authn_authz_9.tif_fmt1.jpg
Abb. 9: Autorisierungscode wird gegen Access-Token getauscht

Mit der Proof-Key-Code-Exchange-Erweiterung – die seit OAuth 2.1 auch verpflichtend ist – fügt man dem Authorization Code Flow noch eine weitere Sicherheitsstufe hinzu. Hierbei generiert der Client einen zufälligen String, bekannt als Code Verifier. Daraufhin erstellt der Client eine Code Challenge aus diesem Verifier, in der Regel, indem er einen Hashwert (SHA256) des Verifiers bildet und den Hash dann in Base64-URL kodiert. Bei der erneuten Authentifizierung des Benutzers werden neben den Credentials die Code Challenge und die Hashfunktion mitgegeben, mit der eben diese Code Challenge erstellt wurde. Daraufhin erhält, wie auch zuvor, die Anwendung einen Autorisierungscode und sendet diesen, diesmal mit dem Code Verifier, an seinen Identity-Provider. Dieser verwendet den empfangenen Code Verifier, um die Code Challenge zu generieren (d. h., er berechnet den SHA256-Hash und kodiert ihn in Base64-URL). Der Identity-Provider vergleicht dann die generierte Challenge mit der ursprünglich vom Client gesendeten Challenge. Stimmen diese überein, weiß der Server, dass die Anfrage vom gleichen Client stammt, der den Autorisierungscode angefordert hat und liefert ebenfalls das Access-Token aus.

Zum Glück entfällt die Notwendigkeit, sich mit den Details der Implementierung dieser Flows auseinanderzusetzen, da das von externen Bibliotheken, wie beispielsweise denen, die Auth0 by Okta bereitstellt, abgedeckt wird. Dennoch ist ein grundlegendes Verständnis der verschiedenen OAuth Flows von unschätzbarem Wert, um eine fundierte und sichere Entscheidung über den am besten geeigneten Flow für unsere spezifischen Anforderungen treffen zu können.

ABTAUCHEN IM DEEP DIVE

Im Fortgeschrittenen Camp tauchen Sie ab unter die Oberfläche einer modernen Angular-Anwendung.
→ Nächster Termin: 2. - 4. Dezember, Berlin

Konfiguration des Identity Provider

Wie bereits erwähnt werde ich in meiner Beispielimplementierung den Identity Provider Auth0 by Okta verwenden. Sämtliche gezeigten Funktionalitäten und Konzepte finden sich aber in anderen Identity-as-a-Service-Lösungen ebenfalls wieder. Auth0 bietet einen kostenlosen Nutzungsplan [9], der bis zu 7 500 aktive Benutzer und unbegrenzte Log-ins umfasst. Diese kostenlosen Pläne sind ideal, um die grundlegenden Funktionen von Auth0 by Okta kennenzulernen und zu testen, wie gut sie sich in Ihre Anwendung oder Ihr Projekt integrieren lassen.

Wenn wir uns nun kostenlos registrieren, können wir eine neue Applikation – auch Tenant genannt – erstellen (Abb. 10). Ein Tenant bezeichnet eine dedizierte Instanz der Identity-Provider-Plattform, die für einen Kunden oder ein Projekt eingerichtet wird. Es ist im Wesentlichen ein separater Container, in dem alle Ihre Benutzer, Sicherheitseinstellungen, Anwendungen und Konfigurationen für Ihre Authentifizierungs- und Autorisierungsvorgänge gespeichert werden. Jeder Tenant bei Auth0 ist durch eine eindeutige Domain gekennzeichnet und isoliert, was bedeutet, dass die Daten und Konfigurationen eines Tenants nicht mit denen anderer Tenants geteilt werden.

kraus_authn_authz_10.tif_fmt1.jpg
Abb. 10: Erstellen einer Applikation

Nachdem der Tenant erfolgreich angelegt wurde können wir diesen nun konfigurieren. Ebenso können wir alle wichtigen Daten direkt einsehen (beispielsweise die Domain und die Client-ID), die wir benötigen, um unsere Frontend-Applikation mit Auth0 zu verbinden (Abb. 11).

kraus_authn_authz_11.tif_fmt1.jpg
Abb. 11: Informationen unserer Applikation

Wichtig hierbei ist, dass wir konfigurieren, welche sogenannten Origins, also URLs auf unseren Tenant zugreifen dürfen. Da wir eine lokale Angular-Anwendung implementieren, müssen wir http://localhost:4200 eintragen. Darüber hinaus ist dieser URL ebenfalls noch in die Liste der Allowed Callback URLs (eine URL-Liste, in der aufgelistet wird, wohin der Identity Provider nach erfolgreicher Authentifizierung wieder navigieren soll) und in die Liste der Allowed Logout URLs (ebenfalls eine Liste der URLs, auf die der Identity Provider uns navigiert, sobald sich der Benutzer erfolgreich ausloggt) einzutragen. Nach erfolgreicher Konfiguration können wir nun damit starten, Auth0 in unserer Angular-Applikation zu integrieren.

Integration in eine Angular-Anwendung

Auth0 erleichtert die Integration erheblich durch die Bereitstellung eines SDK, das als npm-Paket [10] verfügbar ist. Dieses können wir wie folgt installieren:

> npm install @auth/auth0-angular
 

Nach erfolgreicher Installation nutzen wie uns zur Verfügung gestellte provideAuth0-Funktion und konfigurieren sie mit den spezifischen Details unserer Auth0Domain und der Client-ID wie in Listing 1.

Listing 1

//main.ts
import { provideAuth0 } from '@auth0/auth0-angular';
 
bootstrapApplication(AppComponent, {
  providers: [
    provideAuth0({
      domain: 'YOUR_AUTH0_DOMAIN',
      clientId: 'YOUR_AUTH0_CLIENT_ID',
      authorizationParams: {
        redirect_uri: window.location.origin,
      }
    }),
  ]
});
 

Nun fehlen lediglich noch eine Login- und eine Logout-Komponente damit sich unsere Nutzer gegenüber unserem Identity Provider authentifizieren können. Der bereitgestellte AuthService erleichtert und die Implementierung und Verwaltung des vollständigen Authorization Code Flow mit PKCE-Erweiterung (Listing 2).

Listing 2

import { AuthService } from "@auth0/auth0-angular";
 
@Component({
  selector: "app-login-button",
  template: `
    <button class="button__login" (click)="handleLogin()">Log In</button>
  `
})
export class LoginButtonComponent {
  constructor(private auth: AuthService) {
  }
 
  handleLogin(): void {
    this.auth.loginWithRedirect({
      prompt: "login"
    });
  }
}
 

Beim Klick auf den Log-in-Button erfolgt eine Weiterleitung zur Log-in-Maske unseres Identity Providers (Abb. 12). Werfen wir dabei einen Blick auf den Network-Tab unseres Webbrowsers, sehen wir einen Request an den / authorize-Endpoint unseres Identity Provider (Abb. 13). Ebenfalls zu sehen sind die Properties code_challenge_method und code_challenge. Diese sind notwendig für die PKCE-Erweiterung. Die Property response_type mit dem Wert code gibt hierbei an, dass der Client unseren Identity Provider zu einem Authorization Code Flow aufruft.

kraus_authn_authz_12.tif_fmt1.jpg
Abb.12: Log-in-Maske von Auth0
kraus_authn_authz_13.tif_fmt1.jpg
Abb. 13: Payload des Requests

Wenn wir uns nun erfolgreich einloggen, können wir eine weitere Anfrage auf den /token-Endpoint unseres Identity Provider sehen. Der mitgesendete Payload sieht dieses Mal wie folgt aus:

client_id: "afRDjs6KFS0TV5NL7NzltUw9Wlktukutyk77"
code: "bigvau6q0DIA9HTbJKcXyRG09j1lZX1jTa8zLAsCIV2Wh"
code_verifier: "zBtJLPMI1TcKpLcOgHoY5ukNGmVnstxhWUksfh1Rso7"
grant_type: "authorization_code"
redirect_uri: "http://localhost:4040/callback"
 

Wie bereits erwähnt wird bei diesem Aufruf nun der Authorization-Code (code) mitgesendet werden, sowie der Code Verifier (code_verifier), damit unser Identity Provider überprüfen kann, ob beide Anfragen vom selben Client stammen.

Als Antwort erhalten wir das Access-Token sowie ein ID-Token, da OpenID Connect ebenfalls automatisch aktiviert ist (Abb. 14). Diese Tokens werden standardmäßig in einem Cookie abgespeichert. Wir können das SDK so konfigurieren, dass die Tokens im localStorage abgelegt werden, allerdings ist das nur in Verbindung mit einem Refresh-Token ratsam.

kraus_authn_authz_14.tif_fmt1.jpg
Abb. 14: Response mit Access-Token und ID-Token

Das Access-Token können wir nun für die Autorisierung gegenüber verschiedenen Backend-APIs verwenden. Hierfür setzen wir den Authorization-Header bei allen Anfragen auf das entsprechende API. Das kann uns auch von einem von Auth0 bereitgestellten Angular Inteceptor abgenommen werden (Listing 3).

Listing 3

//main.ts
import { provideAuth0, authHttpInterceptorFn } from '@auth0/auth0-angular';
 
bootstrapApplication(AppComponent, {
  providers: [
    provideAuth0(...),
    provideHttpClient(
      withInterceptors([authHttpInterceptorFn])
    )
  ]
});
 

Senden wir nun also eine Anfrage an ein geschütztes API, wird automatisch der Authorization-Header mit dem Wert des Access-Tokens gesetzt. Um die Anwendung vollständig abzurunden, fügen wir noch eine Logout-Komponente hinzu, die erneut den AuthService injiziert der uns eine Log-out-Funktion anbietet (Listing 4).

kraus_authn_authz_15.tif_fmt1.jpg
Abb. 15: Access-Token im Header

Listing 4

import { Component, Inject } from "@angular/core";
import { AuthService } from "@auth0/auth0-angular";
import { DOCUMENT } from "@angular/common";
 
@Component({
  selector: "app-logout-button",
  template: `
    <button class="button__logout" (click)="handleLogout()">Log Out</button>
  `
})
export class LogoutButtonComponent {
  constructor(
    private auth: AuthService,
    @Inject(DOCUMENT) private doc: Document
  ) {
  }
  
  handleLogout(): void {
    this.auth.logout({ returnTo: this.doc.location.origin });
  }
}
 

Dieser AuthService bietet uns noch viele weitere Observables an, z. B. isAuthenticated$, um herauszufinden, ob der Benutzer bereits eingeloggt ist oder nicht. Möchten wir hingegen Daten des Benutzers erhalten, wie beispielsweise den Usernamen, einen URL auf ein Profilbild oder die E-Mail-Adresse, so erhalten wir diese Daten, wenn wir uns auf das user$-Observable des AuthServices abonnieren.

Wie wir gesehen haben, gestaltet sich die Integration eines Identity Provider in eine Angular-Applikation recht einfach. Selbst bei anderen SPA-Frameworks wie React oder Vue.js bietet Auth0 entsprechende SDKs an, um auch dort die Integration mit so wenig Aufwand wie möglich zu gestalten. Wenn Sie nicht Auth0 nutzen möchten, bietet Microsoft eigene SDKs [11] zur Integration von Azure Active Directory. Darüber hinaus gibt es auch vielerlei etablierte Open-Source-SDKs [12] für die Integration verschiedener Identity Provider in eine Angular-Anwendung.

Die Absicherung Ihrer Backend-Ressourcen durch Autorisierung ist ein absolutes Muss einer jeden modernen Webanwendung. Der richtige Umgang mit dem Access-Token bietet vielerlei Fallstricke, wie beispielsweise die Auswahl eines schwachen kryptographischen Verfahrens zur Verschlüsselung ihrer Access-Tokens. Nicht signierte Tokens können leicht manipuliert werden, sodass ein Nutzer sich mehr Rechte zuweisen kann, als er eigentlich hat. Mehr als die Hälfte der Top 10 API Security Risks [13], die von der OWASP-Organisation veröffentlicht werden, handeln von fehlerhafter oder falsch implementierter Autorisierung. Es ist also definitiv ratsam, sich nicht selbst an der Implementierung einer Authentifizierungs- und Autorisierungslösung zu versuchen und auf bestehende Lösungen zu setzen, wie sie von Identity Providern zur Verfügung gestellt werden.

Abschließend lässt sich festhalten, dass die Integration von Authentifizierung und Autorisierung in Angular-Anwendungen eine entscheidende Rolle bei der Gewährleistung von Sicherheit und Benutzerfreundlichkeit spielt. Durch die sorgfältige Implementierung von AuthN und AuthZ können Entwickler robuste und sichere Webanwendungen erstellen, die nicht nur den Schutz sensibler Daten ermöglichen, sondern auch ein nahtloses und effizientes Nutzererlebnis bieten. Die in diesem Artikel vorgestellten Methoden und Best Practices bieten dabei einen umfassenden Leitfaden, um diese komplexen Prozesse erfolgreich in Angular-basierten Projekten umzusetzen. Mit dem wachsenden Fokus auf Websicherheit und Datenschutz ist es für Entwickler unerlässlich, sich kontinuierlich weiterzubilden und die neuesten Technologien und Methoden in diesem dynamischen Bereich zu adaptieren.

 

Links & Literatur

[1] https://it-service.network/it-lexikon/cia-triade

[2] Google Authenticator: https://de.wikipedia.org/wiki/Google_Authenticator

[3] https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#security

[4] https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html

[5] https://www.microsoft.com/de-de/security/business/identity-access/microsoft-entra-id

[6] https://auth0.com/de

[7] https://www.keycloak.org

[8] https://oauth.net/2.1/

[9] https://auth0.com/pricing

[10] https://www.npmjs.com/package/@auth0/auth0-angular

[11] https://learn.microsoft.com/en-us/entra/identity-platform/msal-overview

[12] https://github.com/manfredsteyer/angular-oauth2-oidc und https://github.com/damienbod/angular-auth-oidc-client

[13] https://owasp.org/API-Security/editions/2023/en/0x11-t10/

ALLE NEWS ZUM ANGULAR CAMP!