Tutorial JSON Web Token: Un exemplu în Laravel și AngularJS

Publicat: 2022-03-11

Odată cu popularitatea în creștere a aplicațiilor cu o singură pagină, a aplicațiilor mobile și a serviciilor API RESTful, modul în care dezvoltatorii web scriu codul back-end s-a schimbat semnificativ. Cu tehnologii precum AngularJS și BackboneJS, nu mai petrecem mult timp construind markup, ci construim API-uri pe care le consumă aplicațiile noastre front-end. Back-end-ul nostru este mai mult despre logica de afaceri și date, în timp ce logica de prezentare este mutată exclusiv în aplicațiile front-end sau mobile. Aceste schimbări au condus la noi modalități de implementare a autentificării în aplicațiile moderne.

Autentificarea este una dintre cele mai importante părți ale oricărei aplicații web. Timp de decenii, cookie-urile și autentificarea bazată pe server au fost cea mai simplă soluție. Cu toate acestea, gestionarea autentificării în aplicațiile mobile moderne și cu o singură pagină poate fi dificilă și necesită o abordare mai bună. Cele mai cunoscute soluții la problemele de autentificare pentru API-uri sunt OAuth 2.0 și JSON Web Token (JWT).

Înainte de a intra în acest tutorial JSON Web Token, ce este exact un JWT?

Ce este un token web JSON?

Un token web JSON este utilizat pentru a trimite informații care pot fi verificate și de încredere prin intermediul unei semnături digitale. Acesta cuprinde un obiect JSON compact și sigur pentru URL, care este semnat criptografic pentru a-și verifica autenticitatea și care poate fi, de asemenea, criptat dacă încărcătura utilă conține informații sensibile.

Datorită structurii sale compacte, JWT este de obicei folosit în anteturile Authorization HTTP sau în parametrii de interogare URL.

Structura unui token web JSON

Un JWT este reprezentat ca o secvență de valori codificate URL base64 care sunt separate prin caractere punct.

Exemplu de token web JSON în laravel și angularjs

Iată un exemplu de token JWT:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0 . yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

Antet

Antetul conține metadatele pentru token și conține minim tipul de semnătură și algoritmul de criptare. (Puteți folosi un instrument de formatare JSON pentru a înfrumuseța obiectul JSON.)

Exemplu de antet

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

Acest antet exemplu JWT declară că obiectul codificat este un JSON Web Token și că este semnat folosind algoritmul HMAC SHA-256.

Odată ce acesta este codificat în base64, avem prima parte a JWT-ului nostru.

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Sarcină utilă (revendicări)

În contextul JWT, o revendicare poate fi definită ca o declarație despre o entitate (de obicei, utilizator), precum și metadate suplimentare despre simbolul în sine. Revendicarea conține informațiile pe care dorim să le transmitem și pe care serverul le poate folosi pentru a gestiona corect autentificarea JSON Web Token. Există mai multe revendicări pe care le putem furniza; acestea includ nume de revendicări înregistrate, nume de revendicări publice și nume de revendicări private.

Cereri JWT înregistrate

Acestea sunt revendicările care sunt înregistrate în registrul IANA JSON Web Token Claims. Aceste revendicări JWT nu sunt menite să fie obligatorii, ci mai degrabă să ofere un punct de plecare pentru un set de revendicări utile, interoperabile.

Acestea includ:

  • iss : emitentul jetonului
  • sub : Subiectul jetonului
  • aud : Audiența jetonului
  • exp : timpul de expirare JWT definit în ora Unix
  • nbf : „Nu înainte de” ora care identifică timpul înainte de care JWT nu trebuie acceptat pentru procesare
  • iat : ora „Emis la”, în ora Unix, la care a fost emis jetonul
  • jti : revendicarea ID JWT oferă un identificator unic pentru JWT

Pretenții publice

Afirmațiile publice trebuie să aibă nume rezistente la coliziuni. Făcând numele unui URI sau URN, coliziunile de denumire sunt evitate pentru JWT-urile în care expeditorul și receptorul nu fac parte dintr-o rețea închisă.

Un exemplu de nume de revendicare publică ar putea fi: https://www.toptal.com/jwt_claims/is_admin , iar cea mai bună practică este să plasați un fișier în acea locație care să descrie revendicarea, astfel încât să poată fi dereferințată pentru documentare.

Revendicări private

Numele de revendicare private pot fi folosite în locuri în care JWT-urile sunt schimbate doar într-un mediu închis între sisteme cunoscute, cum ar fi în interiorul unei întreprinderi. Acestea sunt afirmații pe care le putem defini noi înșine, cum ar fi ID-uri de utilizator, roluri de utilizator sau orice altă informație.

Utilizarea numelor de revendicare care ar putea avea semnificații semantice contradictorii în afara unui sistem închis sau privat sunt supuse coliziunii, așa că folosiți-le cu prudență.

Este important de reținut că dorim să păstrăm un token web cât mai mic posibil, așa că folosiți numai datele necesare în cadrul revendicărilor publice și private.

Exemplu de sarcină utilă JWT

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

Acest exemplu de încărcare utilă are două revendicări înregistrate, o revendicare publică și două cereri private. Odată ce este codificat în base64, avem a doua parte a JWT-ului nostru.

 eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0

Semnătură

Standardul JWT urmează specificația JSON Web Signature (JWS) pentru a genera simbolul final semnat. Este generat prin combinarea antetului JWT codificat și a încărcăturii utile JWT codificate și semnarea acestuia folosind un algoritm de criptare puternic, cum ar fi HMAC SHA-256. Cheia secretă a semnăturii este deținută de server, astfel încât acesta va putea verifica jetoanele existente și va putea semna altele noi.

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

Aceasta ne oferă partea finală a JWT-ului nostru.

 yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

Securitate și criptare JWT

Este esențial să utilizați TLS/SSL împreună cu JWT, pentru a preveni atacurile de tip man-in-the-middle. În cele mai multe cazuri, acest lucru va fi suficient pentru a cripta încărcătura utilă JWT dacă conține informații sensibile. Cu toate acestea, dacă dorim să adăugăm un strat suplimentar de protecție, putem cripta încărcătura utilă JWT în sine folosind specificația JSON Web Encryption (JWE).

Desigur, dacă dorim să evităm suprasolicitarea suplimentară a utilizării JWE, o altă opțiune este să păstrăm pur și simplu informațiile sensibile în baza noastră de date și să folosim simbolul nostru pentru apeluri API suplimentare către server ori de câte ori trebuie să accesăm date sensibile.

De ce este nevoie de jetoane web?

Înainte de a putea vedea toate beneficiile utilizării autentificării JWT, trebuie să ne uităm la modul în care a fost făcută autentificarea în trecut.

Autentificare bazată pe server

Autentificare bazată pe server

Deoarece protocolul HTTP este apatrid, trebuie să existe un mecanism pentru stocarea informațiilor despre utilizator și o modalitate de a autentifica utilizatorul la fiecare cerere ulterioară după conectare. Majoritatea site-urilor web folosesc cookie-uri pentru stocarea ID-ului de sesiune al utilizatorului.

Cum functioneaza

Browserul face o solicitare POST către server care conține identificarea și parola utilizatorului. Serverul răspunde cu un cookie, care este setat în browserul utilizatorului și include un ID de sesiune pentru a identifica utilizatorul.

La fiecare cerere ulterioară, serverul trebuie să găsească acea sesiune și să o deserializeze, deoarece datele utilizatorului sunt stocate pe server.

Dezavantajele autentificării bazate pe server

  • Greu de scalat : serverul trebuie să creeze o sesiune pentru un utilizator și să o persiste undeva pe server. Acest lucru se poate face în memorie sau într-o bază de date. Dacă avem un sistem distribuit, trebuie să ne asigurăm că folosim o sesiune de stocare separată care nu este cuplată la serverul de aplicații.

  • Partajarea cererilor de origine încrucișată (CORS) : atunci când folosim apeluri AJAX pentru a prelua o resursă de pe un alt domeniu (“încrucișat”), am putea avea probleme cu cererile interzise, ​​deoarece, în mod implicit, cererile HTTP nu includ cookie-uri pe cereri de origine.

  • Cuplarea cu cadrul web : Când folosim autentificarea bazată pe server, suntem legați de schema de autentificare a cadrului nostru. Este foarte greu, sau chiar imposibil, să partajați datele de sesiune între diferite cadre web scrise în diferite limbaje de programare.

Autentificare bazată pe token

Autentificare bazată pe token

Autentificarea bazată pe token/JWT este fără stat, deci nu este nevoie să stocați informații despre utilizator în sesiune. Acest lucru ne oferă posibilitatea de a ne scala aplicația fără să ne facem griji unde s-a autentificat utilizatorul. Putem folosi cu ușurință același simbol pentru a prelua o resursă securizată dintr-un domeniu altul decât cel la care suntem conectați.

Cum funcționează jetoanele web JSON

Un browser sau un client mobil face o cerere către serverul de autentificare care conține informații de conectare a utilizatorului. Serverul de autentificare generează un nou token de acces JWT și îl returnează clientului. La fiecare solicitare către o resursă restricționată, clientul trimite jetonul de acces în șirul de interogare sau antetul Authorization . Serverul validează apoi jetonul și, dacă este valid, returnează resursa securizată clientului.

Serverul de autentificare poate semna jetonul folosind orice metodă de semnătură securizată. De exemplu, un algoritm de cheie simetrică, cum ar fi HMAC SHA-256, poate fi utilizat dacă există un canal securizat pentru a partaja cheia secretă între toate părțile. Alternativ, poate fi utilizat și un sistem asimetric, cu cheie publică, cum ar fi RSA, eliminând nevoia de partajare suplimentară a cheilor.

Avantajele autentificării bazate pe jetoane

Apatrid, mai ușor de scalat : Jetonul conține toate informațiile pentru a identifica utilizatorul, eliminând necesitatea stării sesiunii. Dacă folosim un echilibrator de încărcare, putem trece utilizatorul pe orice server, în loc să fim legați de același server pe care ne-am autentificat.

Reutilizabilitate : Putem avea multe servere separate, care rulează pe mai multe platforme și domenii, reutilizand același simbol pentru autentificarea utilizatorului. Este ușor să construiți o aplicație care partajează permisiunile cu o altă aplicație.

Securitate JWT : Deoarece nu folosim cookie-uri, nu trebuie să ne protejăm împotriva atacurilor de falsificare a cererilor între site-uri (CSRF). Ar trebui să ne criptăm token-urile folosind JWE dacă trebuie să punem informații sensibile în ele și să ne transmitem token-urile prin HTTPS pentru a preveni atacurile de tip man-in-the-middle.

Performanță : Nu există nicio căutare pe partea de server pentru a găsi și a deserializa sesiunea pentru fiecare solicitare. Singurul lucru pe care trebuie să-l facem este să calculăm HMAC SHA-256 pentru a valida jetonul și a analiza conținutul acestuia.

Un exemplu de token web JSON folosind Laravel 5 și AngularJS

În acest tutorial JWT, voi demonstra cum să implementez autentificarea de bază utilizând jetoane web JSON în două tehnologii web populare: Laravel 5 pentru codul backend și AngularJS pentru exemplul aplicației de pagină o singură pagină (SPA). (Puteți găsi întreaga demonstrație aici și codul sursă în acest depozit GitHub, astfel încât să puteți urma tutorialul.)

Acest exemplu de token web JSON nu va folosi niciun fel de criptare pentru a asigura confidențialitatea informațiilor transmise în revendicări. În practică, acest lucru este adesea în regulă, deoarece TLS/SSL criptează cererea. Cu toate acestea, dacă tokenul va conține informații sensibile, cum ar fi numărul de securitate socială al utilizatorului, ar trebui să fie, de asemenea, criptat folosind JWE.

Exemplu de backend Laravel

Vom folosi Laravel pentru a gestiona înregistrarea utilizatorilor, păstrarea datelor utilizatorului într-o bază de date și furnizarea unor date restricționate care necesită autentificare pentru ca aplicația Angular să le consume. Vom crea un exemplu de subdomeniu API pentru a simula și partajarea resurselor între origini (CORS).

Instalare și bootstrapping de proiect

Pentru a folosi Laravel, trebuie să instalăm managerul de pachete Composer pe mașina noastră. Când dezvoltați în Laravel, vă recomand să folosiți „cutia” preambalată Laravel Homestead de la Vagrant. Ne oferă un mediu de dezvoltare complet, indiferent de sistemul nostru de operare.

Cel mai simplu mod de a porni aplicația noastră JWT Laravel este să folosiți un pachet Composer Laravel Installer.

 composer global require "laravel/installer=~1.1"

Acum suntem cu toții pregătiți să creăm un nou proiect Laravel rulând laravel new jwt .

Pentru orice întrebări despre acest proces, vă rugăm să consultați documentația oficială Laravel.

După ce am creat aplicația de bază Laravel 5, trebuie să setăm Homestead.yaml , care va configura mapările folderelor și configurarea domeniilor pentru mediul nostru local.

Exemplu de fișier 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

După ce ne-am pornit caseta Vagrant cu comanda vagrant up și ne-am conectat la ea folosind vagrant ssh , navigăm la directorul de proiect definit anterior. În exemplul de mai sus, acesta ar fi /home/vagrant/coding/jwt . Acum putem rula comanda php artisan migrate pentru a crea tabelele de utilizator necesare în baza noastră de date.

Instalarea dependențelor Composer

Din fericire, există o comunitate de dezvoltatori care lucrează la Laravel și întrețin multe pachete grozave cu care putem reutiliza și extinde aplicația noastră. În acest exemplu vom folosi tymon/jwt-auth , de Sean Tymon, pentru manipularea jetoanelor pe partea de server și barryvdh/laravel-cors , de Barry vd. Heuvel, pentru manipularea CORS.

jwt-auth

Solicitați pachetul tymon/jwt-auth în composer.json nostru și actualizați dependențele noastre.

 composer require tymon/jwt-auth 0.5.*

Adăugați JWTAuthServiceProvider la matricea noastră de furnizori app/config/app.php .

 'Tymon\JWTAuth\Providers\JWTAuthServiceProvider'

Apoi, în fișierul app/config/app.php , sub matricea de aliases , adăugăm fațada JWTAuth .

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

În cele din urmă, vom dori să publicăm configurația pachetului folosind următoarea comandă: php artisan config:publish tymon/jwt-auth

Tokenurile web JSON sunt criptate folosind o cheie secretă. Putem genera acea cheie folosind comanda php artisan jwt:generate . Acesta va fi plasat în fișierul nostru config/jwt.php . În mediul de producție, totuși, nu dorim niciodată să avem parolele sau cheile API în fișierele de configurare. În schimb, ar trebui să le plasăm în variabilele de mediu ale serverului și să le facem referire în fișierul de configurare cu funcția env . De exemplu:

 'secret' => env('JWT_SECRET')

Putem afla mai multe despre acest pachet și despre toate setările sale de configurare pe Github.

laravel-cors

Solicitați pachetul barryvdh/laravel-cors din composer.json nostru și actualizați dependențele noastre.

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

Adăugați CorsServiceProvider la matricea noastră de furnizori app/config/app.php .

 'Barryvdh\Cors\CorsServiceProvider'

Apoi adăugați middleware-ul în app/Http/Kernel.php .

 'Barryvdh\Cors\Middleware\HandleCors'

Publicați configurația într-un fișier local config/cors.php utilizând comanda php artisan vendor:publish .

Exemplu de configurare a fișierului cors.php :

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

Rutarea și gestionarea solicitărilor HTTP

Din motive de concizie, voi pune tot codul meu în fișierul routes.php care este responsabil pentru rutarea Laravel și delegarea cererilor către controlori. De obicei, vom crea controlere dedicate pentru gestionarea tuturor solicitărilor noastre HTTP și să ne păstrăm codul modular și curat.

Vom încărca vizualizarea noastră AngularJS SPA folosind

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

Înregistrare utilizator

Când facem o solicitare POST către /signup cu un nume de utilizator și o parolă, vom încerca să creăm un nou utilizator și să-l salvăm în baza de date. După ce utilizatorul a fost creat, un JWT este creat și returnat prin răspunsul 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')); });

Conectare utilizator

Când facem o solicitare POST către /signin cu un nume de utilizator și o parolă, verificăm că utilizatorul există și returnează un JWT prin răspunsul 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')); });

Preluarea unei resurse restricționate pe același domeniu

Odată ce utilizatorul este conectat, putem prelua resursa restricționată. Am creat o rută /restricted care simulează o resursă care are nevoie de un utilizator autentificat. Pentru a face acest lucru, antetul de Authorization a cererii sau șirul de interogare trebuie să furnizeze JWT pentru ca backend-ul să îl verifice.

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

În acest exemplu, folosesc middleware-ul jwt-auth furnizat în pachetul jwt-auth folosind 'before' => 'jwt-auth' . Acest middleware este folosit pentru a filtra cererea și a valida jetonul JWT. Dacă simbolul este invalid, nu este prezent sau a expirat, middleware-ul va lansa o excepție pe care o putem prinde.

În Laravel 5, putem prinde excepții folosind fișierul app/Exceptions/Handler.php . Folosind funcția de render putem crea răspunsuri HTTP bazate pe excepția aruncată.

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

Dacă utilizatorul este autentificat și jetonul este valid, putem returna în siguranță datele restricționate către front-end prin JSON.

Preluarea resurselor restricționate din subdomeniul API

În următorul exemplu de simbol web JSON, vom adopta o abordare diferită pentru validarea simbolului. În loc să folosim middleware jwt-auth , vom trata excepțiile manual. Când facem o solicitare POST către un server API api.jwt.dev/v1/restricted , facem o solicitare între origini și trebuie să activăm CORS pe backend. Din fericire, am configurat deja CORS în fișierul 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.']; }); });

Exemplu de front-end AngularJS

Folosim AngularJS ca front-end, bazându-ne pe apelurile API către serverul de autentificare back-end Laravel pentru autentificarea utilizatorilor și date eșantion, plus serverul API pentru date de exemplu de origine încrucișată. Odată ce mergem la pagina de pornire a proiectului nostru, backend-ul va servi resources/views/spa.blade.php care va porni aplicația Angular.

Iată structura de foldere a aplicației 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

Bootstrapping aplicația unghiulară

spa.blade.php conține elementele esențiale necesare pentru a rula aplicația. Vom folosi Twitter Bootstrap pentru stil, împreună cu o temă personalizată de la Bootswatch. Pentru a avea un feedback vizual atunci când facem un apel AJAX, vom folosi scriptul unghiular-loading-bar, care interceptează solicitările XHR și creează o bară de încărcare. În secțiunea antet, avem următoarele foi de stil:

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

Subsolul marcajului nostru conține referințe la biblioteci, precum și scripturile noastre personalizate pentru module, controlere și servicii 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>

Folosim biblioteca ngStorage pentru AngularJS, pentru a salva jetoane în stocarea locală a browserului, astfel încât să o putem trimite la fiecare solicitare prin antetul Authorization .

În mediul de producție, desigur, am minimiza și combina toate fișierele noastre de script și foile de stil pentru a îmbunătăți performanța.

Am creat o bară de navigare folosind Bootstrap care va schimba vizibilitatea link-urilor corespunzătoare, în funcție de starea de conectare a utilizatorului. Starea de conectare este determinată de prezența unei variabile token în domeniul de aplicare al controlerului.

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

Dirijare

Avem un fișier numit app.js , care este responsabil pentru configurarea tuturor rutelor noastre 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: '/' });

Aici putem vedea că am definit patru rute care sunt gestionate fie de HomeController , fie de RestrictedController . Fiecare rută corespunde unei vizualizări HTML parțiale. De asemenea, am definit două constante care conțin adrese URL pentru solicitările noastre HTTP către backend.

Solicitați Interceptor

Serviciul $http al AngularJS ne permite să comunicăm cu backend-ul și să facem solicitări HTTP. În cazul nostru, dorim să interceptăm fiecare cerere HTTP și să o injectăm cu un antet de Authorization care conține JWT-ul nostru dacă utilizatorul este autentificat. De asemenea, putem folosi un interceptor pentru a crea un handler global de erori HTTP. Iată un exemplu de interceptor care injectează un token dacă este disponibil în stocarea locală a browserului.

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

Controlori

În fișierul controllers.js , am definit două controlere pentru aplicația noastră: HomeController și RestrictedController . HomeController se ocupă de funcționalitatea de conectare, înscriere și deconectare. Acesta transmite datele despre numele de utilizator și parola din formularele de conectare și înscriere către serviciul Auth , care trimite solicitări HTTP către backend. Apoi salvează simbolul în stocarea locală sau afișează un mesaj de eroare, în funcție de răspunsul din partea backend-ului.

 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 se comportă în același mod, doar că preia datele utilizând funcțiile getRestrictedData și getApiData din serviciul de 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.'; }); }]);

Backend-ul este responsabil pentru difuzarea datelor restricționate numai dacă utilizatorul este autentificat. Aceasta înseamnă că, pentru a răspunde cu datele restricționate, cererea pentru acele date trebuie să conțină un JWT valid în antetul sau șirul de interogare de Authorization . Dacă nu este cazul, serverul va răspunde cu un cod de stare de eroare neautorizat 401.

Serviciul de autentificare

Serviciul Auth este responsabil pentru autentificarea și înscrierea solicitărilor HTTP la backend. Dacă cererea are succes, răspunsul conține jetonul semnat, care este apoi decodat în base64, iar informațiile despre revendicările jetonului incluse sunt salvate într-o variabilă tokenClaims . Acesta este transmis controlerului prin funcția 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; } }; } ]);

Serviciu de date

Acesta este un serviciu simplu care face solicitări către serverul de autentificare, precum și către serverul API, pentru unele date restrictive false. Ea face cererea și delegă controlorului apelurile de succes și erori.

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

Dincolo de acest tutorial JSON Web Token

Autentificarea bazată pe jetoane ne permite să construim sisteme decuplate care nu sunt legate de o anumită schemă de autentificare. Jetonul poate fi generat oriunde și consumat pe orice sistem care utilizează aceeași cheie secretă pentru semnarea jetonului. Sunt pregătite pentru dispozitive mobile și nu necesită utilizarea cookie-urilor.

Tokenurile web JSON funcționează în toate limbajele de programare populare și câștigă rapid în popularitate. Sunt susținute de companii precum Google, Microsoft și Zendesk. Specificațiile standard ale Internet Engineering Task Force (IETF) sunt încă în versiunea nefinalizată și se pot schimba ușor în viitor.

Mai sunt multe de acoperit despre JWT, cum ar fi modul de gestionare a detaliilor de securitate și reîmprospătarea token-urilor atunci când expiră, dar tutorialul JSON Web Token ar trebui să demonstreze utilizarea de bază și, mai important, avantajele utilizării JWT-urilor.

Citiți suplimentare pe blogul Toptal Engineering:

  • Crearea unui API REST Node.js/TypeScript, partea 3: MongoDB, autentificare și teste automate