Esercitazione sui token Web JSON: un esempio in Laravel e AngularJS

Pubblicato: 2022-03-11

Con la crescente popolarità delle applicazioni a pagina singola, delle applicazioni mobili e dei servizi API RESTful, il modo in cui gli sviluppatori Web scrivono il codice di back-end è cambiato in modo significativo. Con tecnologie come AngularJS e BackboneJS, non dedichiamo più molto tempo alla creazione di markup, bensì alle API che utilizzano le nostre applicazioni front-end. Il nostro back-end è più incentrato sulla logica aziendale e sui dati, mentre la logica di presentazione viene spostata esclusivamente nelle applicazioni front-end o mobili. Queste modifiche hanno portato a nuovi modi di implementare l'autenticazione nelle applicazioni moderne.

L'autenticazione è una delle parti più importanti di qualsiasi applicazione web. Per decenni, i cookie e l'autenticazione basata su server sono stati la soluzione più semplice. Tuttavia, la gestione dell'autenticazione nelle moderne applicazioni mobili e a pagina singola può essere complicata e richiedere un approccio migliore. Le soluzioni più note ai problemi di autenticazione per le API sono OAuth 2.0 e JSON Web Token (JWT).

Prima di entrare in questo tutorial sul token Web JSON, cos'è esattamente un JWT?

Che cos'è un token Web JSON?

Un token Web JSON viene utilizzato per inviare informazioni che possono essere verificate e attendibili mediante una firma digitale. Comprende un oggetto JSON compatto e sicuro per gli URL, che è firmato crittograficamente per verificarne l'autenticità e che può anche essere crittografato se il payload contiene informazioni riservate.

A causa della sua struttura compatta, JWT viene solitamente utilizzato nelle intestazioni di Authorization HTTP o nei parametri di query URL.

Struttura di un token Web JSON

Un JWT è rappresentato come una sequenza di valori codificati in base64url separati da punti.

Esempio di token web JSON in laravel e angularjs

Ecco un esempio di token JWT:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0 . yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

Intestazione

L'intestazione contiene i metadati per il token e contiene in minima parte il tipo di firma e l'algoritmo di crittografia. (Puoi utilizzare uno strumento di formattazione JSON per abbellire l'oggetto JSON.)

Esempio di intestazione

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

Questa intestazione di esempio JWT dichiara che l'oggetto codificato è un token Web JSON e che è firmato utilizzando l'algoritmo HMAC SHA-256.

Una volta che questo è codificato in base64, abbiamo la prima parte del nostro JWT.

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Carico utile (reclami)

Nel contesto di JWT, un'attestazione può essere definita come un'affermazione su un'entità (in genere, l'utente), nonché metadati aggiuntivi sul token stesso. L'attestazione contiene le informazioni che desideriamo trasmettere e che il server può utilizzare per gestire correttamente l'autenticazione del token Web JSON. Ci sono più reclami che possiamo fornire; questi includono nomi di reclami registrati, nomi di reclami pubblici e nomi di reclami privati.

Reclami JWT registrati

Queste sono le attestazioni registrate nel registro delle attestazioni di token Web JSON di IANA. Queste dichiarazioni JWT non intendono essere obbligatorie, ma piuttosto fornire un punto di partenza per una serie di dichiarazioni utili e interoperabili.

Questi includono:

  • iss : l'emittente del token
  • sub : l'oggetto del token
  • aud : il pubblico del token
  • exp : tempo di scadenza JWT definito nell'ora Unix
  • nbf : tempo “Non prima” che identifica il tempo prima del quale il JWT non deve essere accettato per l'elaborazione
  • iat : ora "Emesso alle", in tempo Unix, in cui è stato emesso il token
  • jti : l'attestazione ID JWT fornisce un identificatore univoco per il JWT

Reclami pubblici

Le affermazioni pubbliche devono avere nomi resistenti alle collisioni. Rendendo il nome un URI o un URN, si evitano conflitti di denominazione per i JWT in cui il mittente e il destinatario non fanno parte di una rete chiusa.

Un esempio di nome di un'attestazione pubblica potrebbe essere: https://www.toptal.com/jwt_claims/is_admin e la migliore pratica consiste nel posizionare un file in quella posizione che descrive l'attestazione in modo che possa essere dereferenziato per la documentazione.

Reclami privati

I nomi di attestazione privati ​​possono essere utilizzati in luoghi in cui i JWT vengono scambiati solo in un ambiente chiuso tra sistemi noti, ad esempio all'interno di un'azienda. Si tratta di affermazioni che possiamo definire noi stessi, come ID utente, ruoli utente o qualsiasi altra informazione.

L'uso di nomi di attestazione che potrebbero avere significati semantici contrastanti al di fuori di un sistema chiuso o privato è soggetto a collisioni, quindi usali con cautela.

È importante notare che vogliamo mantenere un token Web il più piccolo possibile, quindi utilizzare solo i dati necessari all'interno di attestazioni pubbliche e private.

Esempio di carico utile JWT

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

Questo carico utile di esempio ha due reclami registrati, un reclamo pubblico e due reclami privati. Una volta codificato in base64, abbiamo la seconda parte del nostro JWT.

 eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0

Firma

Lo standard JWT segue la specifica JSON Web Signature (JWS) per generare il token firmato finale. Viene generato combinando l'intestazione JWT codificata e il payload JWT codificato e firmandolo utilizzando un algoritmo di crittografia avanzato, come HMAC SHA-256. La chiave segreta della firma è conservata dal server, quindi sarà in grado di verificare i token esistenti e firmarne di nuovi.

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

Questo ci dà la parte finale del nostro JWT.

 yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

Sicurezza e crittografia JWT

È fondamentale utilizzare TLS/SSL insieme a JWT, per prevenire attacchi man-in-the-middle. Nella maggior parte dei casi, questo sarà sufficiente per crittografare il payload JWT se contiene informazioni riservate. Tuttavia, se desideriamo aggiungere un ulteriore livello di protezione, possiamo crittografare il payload JWT stesso utilizzando la specifica JSON Web Encryption (JWE).

Naturalmente, se vogliamo evitare il sovraccarico aggiuntivo dell'utilizzo di JWE, un'altra opzione è semplicemente mantenere le informazioni riservate nel nostro database e utilizzare il nostro token per chiamate API aggiuntive al server ogni volta che dobbiamo accedere a dati sensibili.

Perché la necessità di token Web?

Prima di poter vedere tutti i vantaggi dell'utilizzo dell'autenticazione JWT, dobbiamo esaminare il modo in cui l'autenticazione è stata eseguita in passato.

Autenticazione basata su server

Autenticazione basata su server

Poiché il protocollo HTTP è senza stato, è necessario un meccanismo per memorizzare le informazioni sull'utente e un modo per autenticare l'utente a ogni richiesta successiva dopo l'accesso. La maggior parte dei siti Web utilizza i cookie per memorizzare l'ID di sessione dell'utente.

Come funziona

Il browser effettua una richiesta POST al server che contiene l'identificazione e la password dell'utente. Il server risponde con un cookie, che viene impostato sul browser dell'utente, e include un ID di sessione per identificare l'utente.

Ad ogni richiesta successiva, il server deve trovare quella sessione e deserializzarla, perché i dati dell'utente vengono memorizzati sul server.

Svantaggi dell'autenticazione basata su server

  • Difficile da scalare : il server deve creare una sessione per un utente e salvarla da qualche parte nel server. Questo può essere fatto in memoria o in un database. Se disponiamo di un sistema distribuito, dobbiamo assicurarci di utilizzare una memoria di sessione separata che non sia accoppiata al server delle applicazioni.

  • Condivisione delle richieste cross-origin (CORS) : quando si utilizzano le chiamate AJAX per recuperare una risorsa da un altro dominio ("cross-origin") potremmo incorrere in problemi con richieste vietate perché, per impostazione predefinita, le richieste HTTP non includono cookie su cross-origin richieste di origine.

  • Accoppiamento con il framework web : quando si utilizza l'autenticazione basata su server siamo legati allo schema di autenticazione del nostro framework. È davvero difficile, o addirittura impossibile, condividere i dati di sessione tra diversi framework Web scritti in diversi linguaggi di programmazione.

Autenticazione basata su token

Autenticazione basata su token

L'autenticazione basata su token/JWT è senza stato, quindi non è necessario memorizzare le informazioni sull'utente nella sessione. Questo ci dà la possibilità di ridimensionare la nostra applicazione senza preoccuparci di dove l'utente ha effettuato l'accesso. Possiamo facilmente utilizzare lo stesso token per recuperare una risorsa sicura da un dominio diverso da quello a cui abbiamo effettuato l'accesso.

Come funzionano i token Web JSON

Un browser o un client mobile effettua una richiesta al server di autenticazione contenente le informazioni di accesso dell'utente. Il server di autenticazione genera un nuovo token di accesso JWT e lo restituisce al client. Ad ogni richiesta a una risorsa limitata, il client invia il token di accesso nella stringa di query o nell'intestazione di Authorization . Il server quindi convalida il token e, se è valido, restituisce la risorsa sicura al client.

Il server di autenticazione può firmare il token utilizzando qualsiasi metodo di firma sicura. Ad esempio, un algoritmo a chiave simmetrica come HMAC SHA-256 può essere utilizzato se esiste un canale sicuro per condividere la chiave segreta tra tutte le parti. In alternativa, è possibile utilizzare anche un sistema asimmetrico a chiave pubblica, come RSA, eliminando la necessità di un'ulteriore condivisione delle chiavi.

Vantaggi dell'autenticazione basata su token

Stateless, più facile da scalare : il token contiene tutte le informazioni per identificare l'utente, eliminando la necessità dello stato della sessione. Se utilizziamo un sistema di bilanciamento del carico, possiamo passare l'utente a qualsiasi server, invece di essere vincolato allo stesso server su cui abbiamo effettuato l'accesso.

Riutilizzabilità : possiamo avere molti server separati, in esecuzione su più piattaforme e domini, riutilizzando lo stesso token per autenticare l'utente. È facile creare un'applicazione che condivide le autorizzazioni con un'altra applicazione.

Sicurezza JWT : poiché non utilizziamo i cookie, non dobbiamo proteggerci dagli attacchi CSRF (cross-site request forgery). Dovremmo comunque crittografare i nostri token utilizzando JWE se dobbiamo inserire informazioni sensibili al loro interno e trasmettere i nostri token su HTTPS per prevenire attacchi man-in-the-middle.

Prestazioni : non esiste una ricerca lato server per trovare e deserializzare la sessione su ogni richiesta. L'unica cosa che dobbiamo fare è calcolare l'HMAC SHA-256 per convalidare il token e analizzarne il contenuto.

Un esempio di token Web JSON che utilizza Laravel 5 e AngularJS

In questo tutorial JWT dimostrerò come implementare l'autenticazione di base utilizzando i token Web JSON in due tecnologie Web popolari: Laravel 5 per il codice back-end e AngularJS per l'esempio di applicazione a pagina singola (SPA) front-end. (Puoi trovare l'intera demo qui e il codice sorgente in questo repository GitHub in modo da poter seguire il tutorial.)

Questo esempio di token Web JSON non utilizzerà alcun tipo di crittografia per garantire la riservatezza delle informazioni trasmesse nelle attestazioni. In pratica spesso questo va bene, perché TLS/SSL crittografa la richiesta. Tuttavia, se il token conterrà informazioni riservate, come il numero di previdenza sociale dell'utente, dovrebbe anche essere crittografato utilizzando JWE.

Esempio di backend di Laravel

Utilizzeremo Laravel per gestire la registrazione degli utenti, la persistenza dei dati degli utenti in un database e la fornitura di alcuni dati limitati che richiedono l'autenticazione per essere utilizzati dall'app Angular. Creeremo un sottodominio API di esempio per simulare anche la condivisione di risorse cross-origin (CORS).

Installazione e avvio del progetto

Per utilizzare Laravel, dobbiamo installare il gestore di pacchetti Composer sulla nostra macchina. Durante lo sviluppo in Laravel, consiglio di utilizzare la "scatola" preconfezionata di Laravel Homestead di Vagrant. Ci fornisce un ambiente di sviluppo completo indipendentemente dal nostro sistema operativo.

Il modo più semplice per avviare la nostra applicazione JWT Laravel è utilizzare un pacchetto Composer Laravel Installer.

 composer global require "laravel/installer=~1.1"

Ora siamo tutti pronti per creare un nuovo progetto Laravel eseguendo laravel new jwt .

Per qualsiasi domanda su questo processo, fare riferimento alla documentazione ufficiale di Laravel.

Dopo aver creato l'applicazione di base Laravel 5, dobbiamo configurare il nostro Homestead.yaml , che configurerà i mapping delle cartelle e la configurazione dei domini per il nostro ambiente locale.

Esempio di un file Homestead.yaml :

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

Dopo aver avviato la nostra casella Vagrant con il comando vagrant up e aver effettuato l'accesso utilizzando vagrant ssh , passiamo alla directory del progetto precedentemente definita. Nell'esempio sopra questo sarebbe /home/vagrant/coding/jwt . Ora possiamo eseguire il comando php artisan migrate per creare le tabelle utente necessarie nel nostro database.

Installazione delle dipendenze del compositore

Fortunatamente, esiste una comunità di sviluppatori che lavora su Laravel e mantiene molti fantastici pacchetti con cui possiamo riutilizzare ed estendere la nostra applicazione. In questo esempio useremo tymon/jwt-auth , di Sean Tymon, per la gestione dei token lato server, e barryvdh/laravel-cors , di Barry vd. Heuvel, per la gestione di CORS.

jwt-auth

Richiedi il tymon/jwt-auth nel nostro composer.json e aggiorna le nostre dipendenze.

 composer require tymon/jwt-auth 0.5.*

Aggiungi JWTAuthServiceProvider al nostro array di provider app/config/app.php .

 'Tymon\JWTAuth\Providers\JWTAuthServiceProvider'

Successivamente, nel file app/config/app.php , sotto l'array aliases , aggiungiamo la facciata JWTAuth .

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

Infine, vorremo pubblicare il pacchetto config usando il seguente comando: php craft config:publish tymon/jwt-auth

I token Web JSON vengono crittografati utilizzando una chiave segreta. Possiamo generare quella chiave usando il comando php artisan jwt:generate . Verrà inserito nel nostro file config/jwt.php . Nell'ambiente di produzione, tuttavia, non vogliamo mai avere le nostre password o chiavi API all'interno dei file di configurazione. Invece, dovremmo inserirli all'interno delle variabili di ambiente del server e farvi riferimento nel file di configurazione con la funzione env . Per esempio:

 'secret' => env('JWT_SECRET')

Possiamo scoprire di più su questo pacchetto e tutte le sue impostazioni di configurazione su Github.

laravel-cors

Richiedi il barryvdh/laravel-cors nel nostro composer.json e aggiorna le nostre dipendenze.

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

Aggiungi CorsServiceProvider al nostro array di provider app/config/app.php .

 'Barryvdh\Cors\CorsServiceProvider'

Quindi aggiungi il middleware alla nostra app/Http/Kernel.php .

 'Barryvdh\Cors\Middleware\HandleCors'

Pubblica la configurazione in un file config/cors.php locale utilizzando il comando php artisan vendor:publish .

Esempio di configurazione di un file cors.php :

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

Routing e gestione delle richieste HTTP

Per brevità, metterò tutto il mio codice all'interno del file rotte.php che è responsabile dell'instradamento di Laravel e della delega delle richieste ai controller. Di solito creiamo controller dedicati per gestire tutte le nostre richieste HTTP e mantenere il nostro codice modulare e pulito.

Caricheremo la nostra vista SPA AngularJS usando

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

Registrazione Utente

Quando facciamo una richiesta POST a /signup con un nome utente e una password, proveremo a creare un nuovo utente e salvarlo nel database. Dopo che l'utente è stato creato, un JWT viene creato e restituito tramite la risposta JSON.

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

Accesso utente

Quando effettuiamo una richiesta POST a /signin con un nome utente e una password, verifichiamo che l'utente esiste e restituisce un JWT tramite la risposta JSON.

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

Recupero di una risorsa limitata sullo stesso dominio

Una volta che l'utente ha effettuato l'accesso, possiamo recuperare la risorsa limitata. Ho creato una route /restricted che simula una risorsa che necessita di un utente autenticato. Per fare ciò, l'intestazione di Authorization della richiesta o la stringa di query deve fornire il JWT per la verifica del back-end.

 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 questo esempio, sto usando il middleware jwt-auth fornito nel pacchetto jwt-auth usando 'before' => 'jwt-auth' . Questo middleware viene utilizzato per filtrare la richiesta e convalidare il token JWT. Se il token non è valido, non è presente o è scaduto, il middleware genererà un'eccezione che possiamo intercettare.

In Laravel 5, possiamo rilevare le eccezioni usando il file app/Exceptions/Handler.php . Usando la funzione di render possiamo creare risposte HTTP basate sull'eccezione generata.

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

Se l'utente è autenticato e il token è valido, possiamo restituire in sicurezza i dati limitati al frontend tramite JSON.

Recupero delle risorse limitate dal sottodominio dell'API

Nel prossimo esempio di token Web JSON, adotteremo un approccio diverso per la convalida dei token. Invece di usare il middleware jwt-auth , gestiremo le eccezioni manualmente. Quando effettuiamo una richiesta POST a un server API api.jwt.dev/v1/restricted , stiamo facendo una richiesta multiorigine e dobbiamo abilitare CORS sul backend. Fortunatamente, abbiamo già configurato CORS nel file config/cors.php .

 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.']; }); });

Esempio di frontend AngularJS

Utilizziamo AngularJS come front-end, basandoci sulle chiamate API al server di autenticazione back-end di Laravel per l'autenticazione dell'utente e i dati di esempio, oltre al server API per i dati di esempio multiorigine. Una volta che andiamo alla home page del nostro progetto, il backend servirà la resources/views/spa.blade.php che avvierà l'applicazione Angular.

Ecco la struttura delle cartelle dell'app Angular:

 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

Avvio dell'applicazione angolare

spa.blade.php contiene gli elementi essenziali necessari per eseguire l'applicazione. Useremo Twitter Bootstrap per lo stile, insieme a un tema personalizzato da Bootswatch. Per avere un feedback visivo quando si effettua una chiamata AJAX, utilizzeremo lo script angular-loading-bar, che intercetta le richieste XHR e crea una barra di caricamento. Nella sezione dell'intestazione, abbiamo i seguenti fogli di stile:

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

Il piè di pagina del nostro markup contiene riferimenti alle librerie, nonché ai nostri script personalizzati per moduli, controller e servizi Angular.

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

Stiamo utilizzando la libreria ngStorage per AngularJS, per salvare i token nella memoria locale del browser, in modo da poterli inviare a ogni richiesta tramite l'intestazione di Authorization .

Nell'ambiente di produzione, ovviamente, minimizziamo e combiniamo tutti i nostri file di script e fogli di stile per migliorare le prestazioni.

Ho creato una barra di navigazione utilizzando Bootstrap che cambierà la visibilità dei collegamenti appropriati, a seconda dello stato di accesso dell'utente. Lo stato di accesso è determinato dalla presenza di una variabile token nell'ambito del controller.

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

Instradamento

Abbiamo un file chiamato app.js che è responsabile della configurazione di tutti i nostri percorsi front-end.

 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: '/' });

Qui possiamo vedere che abbiamo definito quattro percorsi gestiti da HomeController o RestrictedController . Ogni percorso corrisponde a una vista HTML parziale. Abbiamo anche definito due costanti che contengono gli URL per le nostre richieste HTTP al back-end.

Richiedi Intercettore

Il servizio $http di AngularJS ci permette di comunicare con il backend ed effettuare richieste HTTP. Nel nostro caso vogliamo intercettare ogni richiesta HTTP e iniettarla con un'intestazione Authorization contenente il nostro JWT se l'utente è autenticato. Possiamo anche utilizzare un intercettore per creare un gestore di errori HTTP globale. Ecco un esempio del nostro interceptor che inietta un token se è disponibile nella memoria locale del browser.

 $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); } }; }]);

Controllori

Nel file controllers.js abbiamo definito due controller per la nostra applicazione: HomeController e RestrictedController . HomeController gestisce le funzionalità di accesso, registrazione e disconnessione. Passa i dati di nome utente e password dai moduli di accesso e iscrizione al servizio Auth , che invia le richieste HTTP al back-end. Quindi salva il token nella memoria locale o mostra un messaggio di errore, a seconda della risposta dal back-end.

 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 si comporta allo stesso modo, solo che recupera i dati utilizzando le funzioni getRestrictedData e getApiData nel servizio Data .

 .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.'; }); }]);

Il backend è responsabile della fornitura dei dati riservati solo se l'utente è autenticato. Ciò significa che per rispondere con i dati limitati, la richiesta di tali dati deve contenere un JWT valido all'interno dell'intestazione di Authorization o della stringa di query. In caso contrario, il server risponderà con un codice di stato di errore 401 Non autorizzato.

Servizio di autorizzazione

Il servizio Auth è responsabile dell'accesso e della registrazione delle richieste HTTP al back-end. Se la richiesta ha esito positivo, la risposta contiene il token firmato, che viene quindi decodificato in base64 e le informazioni sulle attestazioni del token incluse vengono salvate in una variabile tokenClaims . Questo viene passato al controller tramite la funzione getTokenClaims .

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

Servizio dati

Questo è un semplice servizio che effettua richieste al server di autenticazione e al server API per alcuni dati con restrizioni fittizie. Effettua la richiesta e delega le callback di successo e di errore al 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) } }; } ]);

Oltre a questo tutorial sui token Web JSON

L'autenticazione basata su token ci consente di costruire sistemi disaccoppiati che non sono legati a un particolare schema di autenticazione. Il token può essere generato ovunque e consumato su qualsiasi sistema che utilizza la stessa chiave segreta per la firma del token. Sono predisposti per dispositivi mobili e non richiedono l'utilizzo di cookie.

I token Web JSON funzionano su tutti i linguaggi di programmazione più diffusi e stanno rapidamente guadagnando popolarità. Sono supportati da aziende come Google, Microsoft e Zendesk. La loro specifica standard di Internet Engineering Task Force (IETF) è ancora nella versione bozza e potrebbe cambiare leggermente in futuro.

C'è ancora molto da trattare sui JWT, ad esempio su come gestire i dettagli di sicurezza e sull'aggiornamento dei token quando scadono, ma l'esercitazione sui token Web JSON dovrebbe dimostrare l'utilizzo di base e, soprattutto, i vantaggi dell'utilizzo dei JWT.

Ulteriori letture sul blog di Toptal Engineering:

  • Creazione di un'API REST Node.js/TypeScript, parte 3: MongoDB, autenticazione e test automatici