JSON Web Token Tutorial: Ein Beispiel in Laravel und AngularJS

Veröffentlicht: 2022-03-11

Mit der steigenden Popularität von Single-Page-Anwendungen, mobilen Anwendungen und RESTful-API-Diensten hat sich die Art und Weise, wie Webentwickler Back-End-Code schreiben, erheblich verändert. Mit Technologien wie AngularJS und BackboneJS verbringen wir nicht mehr viel Zeit mit dem Erstellen von Markup, sondern erstellen APIs, die unsere Front-End-Anwendungen verbrauchen. In unserem Backend geht es mehr um Geschäftslogik und Daten, während die Präsentationslogik ausschließlich in das Frontend oder mobile Anwendungen verlagert wird. Diese Änderungen haben zu neuen Möglichkeiten der Implementierung der Authentifizierung in modernen Anwendungen geführt.

Die Authentifizierung ist einer der wichtigsten Bestandteile jeder Webanwendung. Jahrzehntelang waren Cookies und serverbasierte Authentifizierung die einfachste Lösung. Die Handhabung der Authentifizierung in modernen mobilen und Single-Page-Anwendungen kann jedoch schwierig sein und einen besseren Ansatz erfordern. Die bekanntesten Lösungen für Authentifizierungsprobleme bei APIs sind OAuth 2.0 und JSON Web Token (JWT).

Bevor wir zu diesem JSON-Web-Token-Tutorial kommen, was genau ist ein JWT?

Was ist ein JSON-Web-Token?

Ein JSON Web Token wird verwendet, um Informationen zu senden, die mittels einer digitalen Signatur verifiziert und vertrauenswürdig sind. Es besteht aus einem kompakten und URL-sicheren JSON-Objekt, das kryptografisch signiert ist, um seine Authentizität zu überprüfen, und das auch verschlüsselt werden kann, wenn die Nutzlast vertrauliche Informationen enthält.

Aufgrund seiner kompakten Struktur wird JWT normalerweise in HTTP- Authorization oder URL-Abfrageparametern verwendet.

Aufbau eines JSON Web Token

Ein JWT wird als eine Folge von base64url-codierten Werten dargestellt, die durch Punkte getrennt sind.

JSON-Web-Token-Beispiel in Laravel und Anglejs

Hier ist ein JWT-Token-Beispiel:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0 . yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

Header

Der Header enthält die Metadaten für das Token und enthält mindestens die Art der Signatur und den Verschlüsselungsalgorithmus. (Sie können ein JSON-Formatierungstool verwenden, um das JSON-Objekt zu verschönern.)

Beispiel-Kopfzeile

 { "alg": "HS256", "typ": "JWT" }

Dieser JWT-Beispielheader deklariert, dass das codierte Objekt ein JSON-Web-Token ist und dass es mit dem HMAC SHA-256-Algorithmus signiert ist.

Sobald dies base64-codiert ist, haben wir den ersten Teil unseres JWT.

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Nutzlast (Ansprüche)

Im Kontext von JWT kann ein Anspruch als eine Aussage über eine Entität (normalerweise der Benutzer) sowie zusätzliche Metadaten über das Token selbst definiert werden. Der Anspruch enthält die Informationen, die wir übertragen möchten und die der Server verwenden kann, um die JSON-Webtoken-Authentifizierung ordnungsgemäß zu handhaben. Es gibt mehrere Ansprüche, die wir stellen können; dazu gehören eingetragene Anspruchsnamen, öffentliche Anspruchsnamen und private Anspruchsnamen.

Registrierte JWT-Ansprüche

Dies sind die Ansprüche, die in der IANA JSON Web Token Claims Registry registriert sind. Diese JWT-Ansprüche sind nicht als obligatorisch gedacht, sondern sollen einen Ausgangspunkt für eine Reihe nützlicher, interoperabler Ansprüche bieten.

Diese schließen ein:

  • iss : Der Aussteller des Tokens
  • sub : Der Betreff des Tokens
  • aud : Das Publikum des Tokens
  • exp : JWT-Ablaufzeit, definiert in Unix-Zeit
  • nbf : „Not before“-Zeitpunkt, der den Zeitpunkt angibt, vor dem das JWT nicht zur Verarbeitung angenommen werden darf
  • iat : „Issued at“-Zeit, in Unix-Zeit, zu der das Token ausgestellt wurde
  • jti : Der JWT-ID-Claim stellt eine eindeutige Kennung für das JWT bereit

Öffentliche Ansprüche

Öffentliche Ansprüche müssen kollisionsresistente Namen haben. Indem der Name zu einem URI oder URN gemacht wird, werden Namenskollisionen für JWTs vermieden, bei denen Sender und Empfänger nicht Teil eines geschlossenen Netzwerks sind.

Ein Beispiel für einen öffentlichen Anspruchsnamen könnte sein: https://www.toptal.com/jwt_claims/is_admin , und die beste Vorgehensweise besteht darin, an diesem Ort eine Datei zu platzieren, die den Anspruch beschreibt, damit er zur Dokumentation dereferenziert werden kann.

Private Forderungen

Private Anspruchsnamen können an Orten verwendet werden, an denen JWTs nur in einer geschlossenen Umgebung zwischen bekannten Systemen ausgetauscht werden, z. B. innerhalb eines Unternehmens. Dies sind Behauptungen, die wir selbst definieren können, wie Benutzer-IDs, Benutzerrollen oder andere Informationen.

Die Verwendung von Anspruchsnamen, die möglicherweise widersprüchliche semantische Bedeutungen außerhalb eines geschlossenen oder privaten Systems haben, kann zu Kollisionen führen, verwenden Sie sie also mit Vorsicht.

Es ist wichtig zu beachten, dass wir ein Web-Token so klein wie möglich halten wollen, also verwenden Sie nur notwendige Daten innerhalb öffentlicher und privater Ansprüche.

JWT-Beispielnutzlast

 { "iss": "toptal.com", "exp": 1426420800, "https://www.toptal.com/jwt_claims/is_admin": true, "company": "Toptal", "awesome": true }

Diese Beispielnutzlast hat zwei registrierte Ansprüche, einen öffentlichen Anspruch und zwei private Ansprüche. Sobald es base64-codiert ist, haben wir den zweiten Teil unseres JWT.

 eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0

Unterschrift

Der JWT-Standard folgt der JSON Web Signature (JWS)-Spezifikation, um das endgültige signierte Token zu generieren. Es wird generiert, indem der codierte JWT-Header und die codierte JWT-Nutzlast kombiniert und mit einem starken Verschlüsselungsalgorithmus wie HMAC SHA-256 signiert werden. Der geheime Schlüssel der Signatur wird vom Server gespeichert, damit er vorhandene Token überprüfen und neue signieren kann.

 $encodedContent = base64UrlEncode(header) + "." + base64UrlEncode(payload); $signature = hashHmacSHA256($encodedContent);

Dies gibt uns den letzten Teil unseres JWT.

 yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

JWT-Sicherheit und -Verschlüsselung

Es ist wichtig, TLS/SSL in Verbindung mit JWT zu verwenden, um Man-in-the-Middle-Angriffe zu verhindern. In den meisten Fällen reicht dies aus, um die JWT-Nutzlast zu verschlüsseln, wenn sie vertrauliche Informationen enthält. Wenn wir jedoch eine zusätzliche Schutzebene hinzufügen möchten, können wir die JWT-Nutzlast selbst mit der Spezifikation JSON Web Encryption (JWE) verschlüsseln.

Wenn wir den zusätzlichen Aufwand durch die Verwendung von JWE vermeiden möchten, besteht natürlich eine andere Möglichkeit darin, vertrauliche Informationen einfach in unserer Datenbank zu speichern und unser Token für zusätzliche API-Aufrufe an den Server zu verwenden, wann immer wir auf vertrauliche Daten zugreifen müssen.

Warum die Notwendigkeit von Web-Tokens?

Bevor wir alle Vorteile der Verwendung der JWT-Authentifizierung sehen können, müssen wir uns ansehen, wie die Authentifizierung in der Vergangenheit durchgeführt wurde.

Serverbasierte Authentifizierung

Serverbasierte Authentifizierung

Da das HTTP-Protokoll zustandslos ist, muss es einen Mechanismus zum Speichern von Benutzerinformationen und eine Möglichkeit geben, den Benutzer bei jeder nachfolgenden Anfrage nach der Anmeldung zu authentifizieren. Die meisten Websites verwenden Cookies zum Speichern der Sitzungs-ID des Benutzers.

Wie es funktioniert

Der Browser sendet eine POST-Anfrage an den Server, die die Identifikation und das Passwort des Benutzers enthält. Der Server antwortet mit einem Cookie, das im Browser des Benutzers gesetzt wird und eine Sitzungs-ID enthält, um den Benutzer zu identifizieren.

Bei jeder nachfolgenden Anfrage muss der Server diese Sitzung finden und sie deserialisieren, da Benutzerdaten auf dem Server gespeichert werden.

Nachteile der serverbasierten Authentifizierung

  • Schwer zu skalieren : Der Server muss eine Sitzung für einen Benutzer erstellen und diese irgendwo auf dem Server beibehalten. Dies kann im Speicher oder in einer Datenbank erfolgen. Wenn wir ein verteiltes System haben, müssen wir sicherstellen, dass wir einen separaten Sitzungsspeicher verwenden, der nicht mit dem Anwendungsserver gekoppelt ist.

  • Cross-Origin Request Sharing (CORS) : Bei der Verwendung von AJAX-Aufrufen zum Abrufen einer Ressource von einer anderen Domain („Cross-Origin“) könnten wir auf Probleme mit verbotenen Anfragen stoßen, da HTTP-Anfragen standardmäßig keine Cookies auf Cross-Origin enthalten. Ursprungsanfragen.

  • Kopplung mit dem Web-Framework : Bei der serverbasierten Authentifizierung sind wir an das Authentifizierungsschema unseres Frameworks gebunden. Es ist wirklich schwierig oder sogar unmöglich, Sitzungsdaten zwischen verschiedenen Web-Frameworks auszutauschen, die in verschiedenen Programmiersprachen geschrieben sind.

Tokenbasierte Authentifizierung

Tokenbasierte Authentifizierung

Tokenbasierte/JWT-Authentifizierung ist zustandslos, sodass keine Benutzerinformationen in der Sitzung gespeichert werden müssen. Dies gibt uns die Möglichkeit, unsere Anwendung zu skalieren, ohne uns Gedanken darüber machen zu müssen, wo sich der Benutzer angemeldet hat. Wir können problemlos dasselbe Token verwenden, um eine sichere Ressource von einer anderen Domäne als der, bei der wir angemeldet sind, abzurufen.

Funktionsweise von JSON-Web-Token

Ein Browser oder mobiler Client stellt eine Anfrage an den Authentifizierungsserver, die Anmeldeinformationen des Benutzers enthält. Der Authentifizierungsserver generiert ein neues JWT-Zugriffstoken und gibt es an den Client zurück. Bei jeder Anforderung an eine eingeschränkte Ressource sendet der Client das Zugriffstoken in der Abfragezeichenfolge oder im Authorization -Header. Der Server validiert dann das Token und gibt die sichere Ressource an den Client zurück, wenn es gültig ist.

Der Authentifizierungsserver kann das Token mit einer beliebigen sicheren Signaturmethode signieren. Beispielsweise kann ein symmetrischer Schlüsselalgorithmus wie HMAC SHA-256 verwendet werden, wenn es einen sicheren Kanal gibt, um den geheimen Schlüssel zwischen allen Parteien zu teilen. Alternativ kann auch ein asymmetrisches System mit öffentlichen Schlüsseln wie RSA verwendet werden, wodurch die Notwendigkeit einer weiteren gemeinsamen Nutzung von Schlüsseln entfällt.

Vorteile der Token-basierten Authentifizierung

Zustandslos, einfacher zu skalieren : Das Token enthält alle Informationen zur Identifizierung des Benutzers, sodass der Sitzungsstatus nicht mehr erforderlich ist. Wenn wir einen Load Balancer verwenden, können wir den Benutzer an jeden Server weiterleiten, anstatt an denselben Server gebunden zu sein, auf dem wir uns angemeldet haben.

Wiederverwendbarkeit : Wir können viele separate Server haben, die auf mehreren Plattformen und Domänen laufen und denselben Token zur Authentifizierung des Benutzers wiederverwenden. Es ist einfach, eine Anwendung zu erstellen, die Berechtigungen mit einer anderen Anwendung teilt.

JWT-Sicherheit : Da wir keine Cookies verwenden, müssen wir uns nicht vor Cross-Site Request Forgery (CSRF)-Angriffen schützen. Wir sollten unsere Token immer noch mit JWE verschlüsseln, wenn wir sensible Informationen darin speichern müssen, und unsere Token über HTTPS übertragen, um Man-in-the-Middle-Angriffe zu verhindern.

Leistung : Es gibt keine serverseitige Suche, um die Sitzung bei jeder Anfrage zu finden und zu deserialisieren. Das einzige, was wir tun müssen, ist den HMAC SHA-256 zu berechnen, um das Token zu validieren und seinen Inhalt zu analysieren.

Ein JSON-Web-Token-Beispiel mit Laravel 5 und AngularJS

In diesem JWT-Tutorial werde ich demonstrieren, wie die grundlegende Authentifizierung mithilfe von JSON-Webtoken in zwei gängigen Webtechnologien implementiert wird: Laravel 5 für den Backend-Code und AngularJS für das Frontend-Beispiel für Single Page Application (SPA). (Sie finden die gesamte Demo hier und den Quellcode in diesem GitHub-Repository, damit Sie dem Tutorial folgen können.)

Dieses JSON-Web-Token-Beispiel verwendet keinerlei Verschlüsselung, um die Vertraulichkeit der in den Ansprüchen übermittelten Informationen zu gewährleisten. In der Praxis ist das oft in Ordnung, weil TLS/SSL die Anfrage verschlüsselt. Wenn das Token jedoch vertrauliche Informationen wie die Sozialversicherungsnummer des Benutzers enthalten soll, sollte es auch mit JWE verschlüsselt werden.

Laravel-Backend-Beispiel

Wir werden Laravel verwenden, um die Benutzerregistrierung zu handhaben, Benutzerdaten in einer Datenbank zu speichern und einige eingeschränkte Daten bereitzustellen, die eine Authentifizierung erfordern, damit die Angular-App sie nutzen kann. Wir werden eine Beispiel-API-Subdomäne erstellen, um auch die ursprungsübergreifende Ressourcenfreigabe (CORS) zu simulieren.

Installations- und Projekt-Bootstrapping

Um Laravel nutzen zu können, müssen wir den Composer-Paketmanager auf unserem Rechner installieren. Bei der Entwicklung in Laravel empfehle ich die Verwendung der vorverpackten „Box“ von Laravel Homestead von Vagrant. Es stellt uns unabhängig von unserem Betriebssystem eine vollständige Entwicklungsumgebung zur Verfügung.

Der einfachste Weg, unsere JWT Laravel-Anwendung zu booten, ist die Verwendung eines Composer-Pakets Laravel Installer.

 composer global require "laravel/installer=~1.1"

Jetzt sind wir alle bereit, ein neues Laravel-Projekt zu erstellen, indem laravel new jwt .

Bei Fragen zu diesem Vorgang konsultieren Sie bitte die offizielle Laravel-Dokumentation.

Nachdem wir die grundlegende Laravel 5-Anwendung erstellt haben, müssen wir unsere Homestead.yaml einrichten, die Ordnerzuordnungen und Domänenkonfigurationen für unsere lokale Umgebung konfiguriert.

Beispiel einer Homestead.yaml -Datei:

 --- ip: "192.168.10.10" memory: 2048 cpus: 1 authorize: /Users/ttkalec/.ssh/public.psk keys: - /Users/ttkalec/.ssh/private.ppk folders: - map: /coding/jwt to: /home/vagrant/coding/jwt sites: - map: jwt.dev to: /home/vagrant/coding/jwt/public - map: api.jwt.dev to: /home/vagrant/coding/jwt/public variables: - key: APP_ENV value: local

Nachdem wir unsere Vagrant-Box mit dem Befehl vagrant up hochgefahren und uns mit vagrant ssh eingeloggt haben, navigieren wir in das zuvor definierte Projektverzeichnis. Im obigen Beispiel wäre dies /home/vagrant/coding/jwt . Wir können jetzt den php artisan migrate ausführen, um die erforderlichen Benutzertabellen in unserer Datenbank zu erstellen.

Composer-Abhängigkeiten installieren

Glücklicherweise gibt es eine Community von Entwicklern, die an Laravel arbeiten und viele großartige Pakete pflegen, mit denen wir unsere Anwendung wiederverwenden und erweitern können. In diesem Beispiel verwenden wir tymon/jwt-auth von Sean Tymon für die Handhabung von Token auf der Serverseite und barryvdh/laravel-cors von Barry vd. Heuvel, für den Umgang mit CORS.

jwt-auth

Fordern Sie das tymon/jwt-auth Paket in unserer composer.json an und aktualisieren Sie unsere Abhängigkeiten.

 composer require tymon/jwt-auth 0.5.*

Fügen Sie den JWTAuthServiceProvider zu unserem Anbieter-Array app/config/app.php .

 'Tymon\JWTAuth\Providers\JWTAuthServiceProvider'

Als Nächstes fügen wir in der Datei app/config/app.php unter dem aliases -Array die JWTAuth Fassade hinzu.

 'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth'

Schließlich wollen wir die Paketkonfiguration mit dem folgenden Befehl veröffentlichen: php artisan config:publish tymon/jwt-auth

JSON-Web-Token werden mit einem geheimen Schlüssel verschlüsselt. Wir können diesen Schlüssel mit dem php artisan jwt:generate . Es wird in unsere Datei config/jwt.php . In der Produktionsumgebung möchten wir jedoch niemals unsere Passwörter oder API-Schlüssel in Konfigurationsdateien haben. Stattdessen sollten wir sie in Serverumgebungsvariablen platzieren und sie in der Konfigurationsdatei mit der env -Funktion referenzieren. Zum Beispiel:

 'secret' => env('JWT_SECRET')

Wir können mehr über dieses Paket und alle seine Konfigurationseinstellungen auf Github erfahren.

laravel-cors

Fordern Sie das barryvdh/laravel-cors Paket in unserer composer.json an und aktualisieren Sie unsere Abhängigkeiten.

 composer require barryvdh/laravel-cors 0.4.x@dev

Fügen Sie den CorsServiceProvider zu unserem Provider-Array app/config/app.php .

 'Barryvdh\Cors\CorsServiceProvider'

Fügen Sie dann die Middleware zu unserer app/Http/Kernel.php .

 'Barryvdh\Cors\Middleware\HandleCors'

Veröffentlichen Sie die Konfiguration in einer lokalen config/cors.php -Datei, indem Sie den php artisan vendor:publish verwenden.

Beispiel für die Konfiguration einer cors.php -Datei:

 return [ 'defaults' => [ 'supportsCredentials' => false, 'allowedOrigins' => [], 'allowedHeaders' => [], 'allowedMethods' => [], 'exposedHeaders' => [], 'maxAge' => 0, 'hosts' => [], ], 'paths' => [ 'v1/*' => [ 'allowedOrigins' => ['*'], 'allowedHeaders' => ['*'], 'allowedMethods' => ['*'], 'maxAge' => 3600, ], ], ];

Routing und Verarbeitung von HTTP-Anforderungen

Der Kürze halber werde ich meinen gesamten Code in die Datei "routes.php" einfügen, die für das Laravel-Routing und das Delegieren von Anfragen an Controller verantwortlich ist. Normalerweise erstellen wir dedizierte Controller für die Verarbeitung all unserer HTTP-Anforderungen und halten unseren Code modular und sauber.

Wir laden unsere AngularJS SPA-Ansicht mit

 Route::get('/', function () { return view('spa'); });

Benutzer Registration

Wenn wir eine POST -Anfrage an /signup mit einem Benutzernamen und Passwort stellen, versuchen wir, einen neuen Benutzer zu erstellen und ihn in der Datenbank zu speichern. Nachdem der Benutzer erstellt wurde, wird ein JWT erstellt und per JSON-Antwort zurückgegeben.

 Route::post('/signup', function () { $credentials = Input::only('email', 'password'); try { $user = User::create($credentials); } catch (Exception $e) { return Response::json(['error' => 'User already exists.'], HttpResponse::HTTP_CONFLICT); } $token = JWTAuth::fromUser($user); return Response::json(compact('token')); });

Benutzeranmeldung

Wenn wir eine POST -Anfrage an /signin mit einem Benutzernamen und Passwort stellen, überprüfen wir, ob der Benutzer existiert, und geben ein JWT über die JSON-Antwort zurück.

 Route::post('/signin', function () { $credentials = Input::only('email', 'password'); if ( ! $token = JWTAuth::attempt($credentials)) { return Response::json(false, HttpResponse::HTTP_UNAUTHORIZED); } return Response::json(compact('token')); });

Abrufen einer eingeschränkten Ressource in derselben Domäne

Sobald der Benutzer angemeldet ist, können wir die eingeschränkte Ressource abrufen. Ich habe eine Route /restricted erstellt, die eine Ressource simuliert, die einen authentifizierten Benutzer benötigt. Dazu muss der Authorization -Header oder die Abfragezeichenfolge der Anforderung das JWT für die Überprüfung durch das Back-End bereitstellen.

 Route::get('/restricted', [ 'before' => 'jwt-auth', function () { $token = JWTAuth::getToken(); $user = JWTAuth::toUser($token); return Response::json([ 'data' => [ 'email' => $user->email, 'registered_at' => $user->created_at->toDateTimeString() ] ]); } ]);

In diesem Beispiel verwende ich die im jwt-auth Paket bereitgestellte jwt-auth Middleware mit 'before' => 'jwt-auth' . Diese Middleware wird verwendet, um die Anfrage zu filtern und das JWT-Token zu validieren. Wenn das Token ungültig, nicht vorhanden oder abgelaufen ist, löst die Middleware eine Ausnahme aus, die wir abfangen können.

In Laravel 5 können wir Ausnahmen mit der Datei app/Exceptions/Handler.php . Mit der render können wir HTTP-Antworten basierend auf der ausgelösten Ausnahme erstellen.

 public function render($request, Exception $e) { if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException) { return response(['Token is invalid'], 401); } if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException) { return response(['Token has expired'], 401); } return parent::render($request, $e); }

Wenn der Benutzer authentifiziert und das Token gültig ist, können wir die eingeschränkten Daten sicher über JSON an das Frontend zurückgeben.

Abrufen eingeschränkter Ressourcen aus der API-Subdomain

Im nächsten JSON-Web-Token-Beispiel verfolgen wir einen anderen Ansatz für die Token-Validierung. Anstatt jwt-auth Middleware zu verwenden, behandeln wir Ausnahmen manuell. Wenn wir eine POST -Anfrage an einen API-Server api.jwt.dev/v1/restricted , stellen wir eine ursprungsübergreifende Anfrage und müssen CORS im Backend aktivieren. Glücklicherweise haben wir CORS bereits in der Datei config/cors.php konfiguriert.

 Route::group(['domain' => 'api.jwt.dev', 'prefix' => 'v1'], function () { Route::get('/restricted', function () { try { JWTAuth::parseToken()->toUser(); } catch (Exception $e) { return Response::json(['error' => $e->getMessage()], HttpResponse::HTTP_UNAUTHORIZED); } return ['data' => 'This has come from a dedicated API subdomain with restricted access.']; }); });

AngularJS-Frontend-Beispiel

Wir verwenden AngularJS als Front-End und verlassen uns auf die API-Aufrufe an den Laravel-Back-End-Authentifizierungsserver für die Benutzerauthentifizierung und Beispieldaten sowie den API-Server für ursprungsübergreifende Beispieldaten. Sobald wir auf die Homepage unseres Projekts gehen, stellt das Backend die Ansicht „ resources/views/spa.blade.php “ bereit, die die Angular-Anwendung booten wird.

Hier ist die Ordnerstruktur der Angular-App:

 public/ |-- css/ `-- bootstrap.superhero.min.css |-- lib/ |-- loading-bar.css |-- loading-bar.js `-- ngStorage.js |-- partials/ |-- home.html |-- restricted.html |-- signin.html `-- signup.html `-- scripts/ |-- app.js |-- controllers.js `-- services.js

Bootstrapping der Angular-Anwendung

spa.blade.php enthält das Nötigste, um die Anwendung auszuführen. Wir verwenden Twitter Bootstrap für das Styling zusammen mit einem benutzerdefinierten Design von Bootswatch. Um bei einem AJAX-Aufruf ein visuelles Feedback zu erhalten, verwenden wir das Skript angle-loading-bar, das XHR-Anforderungen abfängt und einen Ladebalken erstellt. Im Header-Bereich haben wir die folgenden Stylesheets:

 <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/bootstrap.superhero.min.css"> <link rel="stylesheet" href="/lib/loading-bar.css">

Die Fußzeile unseres Markups enthält Verweise auf Bibliotheken sowie unsere benutzerdefinierten Skripte für Angular-Module, -Controller und -Dienste.

 <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular-route.min.js"></script> <script src="/lib/ngStorage.js"></script> <script src="/lib/loading-bar.js"></script> <script src="/scripts/app.js"></script> <script src="/scripts/controllers.js"></script> <script src="/scripts/services.js"></script> </body>

Wir verwenden die ngStorage Bibliothek für AngularJS, um Token im lokalen Speicher des Browsers zu speichern, damit wir sie bei jeder Anfrage über den Authorization -Header senden können.

In der Produktionsumgebung würden wir natürlich alle unsere Skriptdateien und Stylesheets verkleinern und kombinieren, um die Leistung zu verbessern.

Ich habe mit Bootstrap eine Navigationsleiste erstellt, die die Sichtbarkeit der entsprechenden Links abhängig vom Anmeldestatus des Benutzers ändert. Der Anmeldestatus wird durch das Vorhandensein einer token im Bereich des Controllers bestimmt.

 <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">JWT Angular example</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li data-ng-show="token"><a ng-href="#/restricted">Restricted area</a></li> <li data-ng-hide="token"><a ng-href="#/signin">Sign in</a></li> <li data-ng-hide="token"><a ng-href="#/signup">Sign up</a></li> <li data-ng-show="token"><a ng-click="logout()">Logout</a></li> </ul> </div>

Routing

Wir haben eine Datei namens app.js , die für die Konfiguration aller unserer Front-End-Routen verantwortlich ist.

 angular.module('app', [ 'ngStorage', 'ngRoute', 'angular-loading-bar' ]) .constant('urls', { BASE: 'http://jwt.dev:8000', BASE_API: 'http://api.jwt.dev:8000/v1' }) .config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) { $routeProvider. when('/', { templateUrl: 'partials/home.html', controller: 'HomeController' }). when('/signin', { templateUrl: 'partials/signin.html', controller: 'HomeController' }). when('/signup', { templateUrl: 'partials/signup.html', controller: 'HomeController' }). when('/restricted', { templateUrl: 'partials/restricted.html', controller: 'RestrictedController' }). otherwise({ redirectTo: '/' });

Hier können wir sehen, dass wir vier Routen definiert haben, die entweder von HomeController oder RestrictedController behandelt werden. Jede Route entspricht einer partiellen HTML-Ansicht. Wir haben auch zwei Konstanten definiert, die URLs für unsere HTTP-Anfragen an das Backend enthalten.

Abfangjäger anfordern

Der $http-Dienst von AngularJS ermöglicht es uns, mit dem Backend zu kommunizieren und HTTP-Anfragen zu stellen. In unserem Fall möchten wir jede HTTP-Anfrage abfangen und ihr einen Authorization -Header mit unserem JWT hinzufügen, wenn der Benutzer authentifiziert ist. Wir können auch einen Interceptor verwenden, um einen globalen HTTP-Error-Handler zu erstellen. Hier ist ein Beispiel für unseren Interceptor, der ein Token einfügt, wenn es im lokalen Speicher des Browsers verfügbar ist.

 $httpProvider.interceptors.push(['$q', '$location', '$localStorage', function ($q, $location, $localStorage) { return { 'request': function (config) { config.headers = config.headers || {}; if ($localStorage.token) { config.headers.Authorization = 'Bearer ' + $localStorage.token; } return config; }, 'responseError': function (response) { if (response.status === 401 || response.status === 403) { $location.path('/signin'); } return $q.reject(response); } }; }]);

Controller

In der Datei controllers.js haben wir zwei Controller für unsere Anwendung definiert: HomeController und RestrictedController . HomeController übernimmt die Anmelde-, Anmelde- und Abmeldefunktion. Es übergibt die Benutzernamen- und Passwortdaten aus den Anmelde- und Registrierungsformularen an den Auth -Dienst, der HTTP-Anforderungen an das Back-End sendet. Je nach Antwort des Backends speichert es dann das Token im lokalen Speicher oder zeigt eine Fehlermeldung an.

 angular.module('app') .controller('HomeController', ['$rootScope', '$scope', '$location', '$localStorage', 'Auth', function ($rootScope, $scope, $location, $localStorage, Auth) { function successAuth(res) { $localStorage.token = res.token; window.location = "/"; } $scope.signin = function () { var formData = { email: $scope.email, password: $scope.password }; Auth.signin(formData, successAuth, function () { $rootScope.error = 'Invalid credentials.'; }) }; $scope.signup = function () { var formData = { email: $scope.email, password: $scope.password }; Auth.signup(formData, successAuth, function () { $rootScope.error = 'Failed to signup'; }) }; $scope.logout = function () { Auth.logout(function () { window.location = "/" }); }; $scope.token = $localStorage.token; $scope.tokenClaims = Auth.getTokenClaims(); }])

RestrictedController verhält sich genauso, nur dass die Daten mithilfe der Funktionen getRestrictedData und getApiData im Data abgerufen werden.

 .controller('RestrictedController', ['$rootScope', '$scope', 'Data', function ($rootScope, $scope, Data) { Data.getRestrictedData(function (res) { $scope.data = res.data; }, function () { $rootScope.error = 'Failed to fetch restricted content.'; }); Data.getApiData(function (res) { $scope.api = res.data; }, function () { $rootScope.error = 'Failed to fetch restricted API content.'; }); }]);

Das Back-End ist nur dann für die Bereitstellung der eingeschränkten Daten verantwortlich, wenn der Benutzer authentifiziert ist. Das bedeutet, dass die Anforderung dieser Daten, um mit den eingeschränkten Daten zu antworten, ein gültiges JWT in ihrem Authorization oder ihrer Abfragezeichenfolge enthalten muss. Ist dies nicht der Fall, antwortet der Server mit dem Fehlerstatuscode 401 Unauthorized.

Authentifizierungsdienst

Der Authentifizierungsdienst ist für die Anmeldung und Registrierung von HTTP-Anforderungen an das Back-End verantwortlich. Wenn die Anforderung erfolgreich ist, enthält die Antwort das signierte Token, das dann base64-decodiert wird, und die eingeschlossenen Tokenanspruchsinformationen werden in einer tokenClaims Variablen gespeichert. Diese wird über die getTokenClaims Funktion an den Controller übergeben.

 angular.module('app') .factory('Auth', ['$http', '$localStorage', 'urls', function ($http, $localStorage, urls) { function urlBase64Decode(str) { var output = str.replace('-', '+').replace('_', '/'); switch (output.length % 4) { case 0: break; case 2: output += '=='; break; case 3: output += '='; break; default: throw 'Illegal base64url string!'; } return window.atob(output); } function getClaimsFromToken() { var token = $localStorage.token; var user = {}; if (typeof token !== 'undefined') { var encoded = token.split('.')[1]; user = JSON.parse(urlBase64Decode(encoded)); } return user; } var tokenClaims = getClaimsFromToken(); return { signup: function (data, success, error) { $http.post(urls.BASE + '/signup', data).success(success).error(error) }, signin: function (data, success, error) { $http.post(urls.BASE + '/signin', data).success(success).error(error) }, logout: function (success) { tokenClaims = {}; delete $localStorage.token; success(); }, getTokenClaims: function () { return tokenClaims; } }; } ]);

Datendienst

Dies ist ein einfacher Dienst, der Anforderungen an den Authentifizierungsserver sowie den API-Server für einige eingeschränkte Dummy-Daten stellt. Es stellt die Anforderung und delegiert Erfolgs- und Fehlerrückrufe an den Controller.

 angular.module('app') .factory('Data', ['$http', 'urls', function ($http, urls) { return { getRestrictedData: function (success, error) { $http.get(urls.BASE + '/restricted').success(success).error(error) }, getApiData: function (success, error) { $http.get(urls.BASE_API + '/restricted').success(success).error(error) } }; } ]);

Über dieses JSON-Web-Token-Tutorial hinaus

Die Token-basierte Authentifizierung ermöglicht es uns, entkoppelte Systeme aufzubauen, die nicht an ein bestimmtes Authentifizierungsschema gebunden sind. Das Token kann überall generiert und auf jedem System verwendet werden, das denselben geheimen Schlüssel zum Signieren des Tokens verwendet. Sie sind für Mobilgeräte geeignet und erfordern keine Verwendung von Cookies.

JSON Web Tokens funktionieren in allen gängigen Programmiersprachen und gewinnen schnell an Popularität. Sie werden von Unternehmen wie Google, Microsoft und Zendesk unterstützt. Ihre Standardspezifikation der Internet Engineering Task Force (IETF) befindet sich noch in der Entwurfsversion und kann sich in Zukunft geringfügig ändern.

Es gibt noch viel über JWTs zu berichten, z. B. über den Umgang mit Sicherheitsdetails und das Aktualisieren von Token, wenn sie ablaufen, aber das JSON Web Token-Tutorial sollte die grundlegende Verwendung und, was noch wichtiger ist, die Vorteile der Verwendung von JWTs demonstrieren.

Weiterführende Literatur im Toptal Engineering Blog:

  • Erstellen einer Node.js/TypeScript-REST-API, Teil 3: MongoDB, Authentifizierung und automatisierte Tests