JSON Web Tokenチュートリアル:LaravelとAngularJSの例

公開: 2022-03-11

シングルページアプリケーション、モバイルアプリケーション、RESTful APIサービスの人気が高まるにつれ、Web開発者がバックエンドコードを作成する方法が大幅に変わりました。 AngularJSやBackboneJSなどのテクノロジーにより、マークアップの構築に多くの時間を費やす必要がなくなり、代わりにフロントエンドアプリケーションが使用するAPIを構築しています。 私たちのバックエンドはビジネスロジックとデータに関するものですが、プレゼンテーションロジックはフロントエンドまたはモバイルアプリケーションにのみ移動されます。 これらの変更により、最新のアプリケーションに認証を実装する新しい方法が生まれました。

認証は、Webアプリケーションの最も重要な部分の1つです。 何十年もの間、Cookieとサーバーベースの認証が最も簡単なソリューションでした。 ただし、最新のモバイルおよびシングルページアプリケーションでの認証の処理は難しい場合があり、より良いアプローチが必要になります。 APIの認証の問題に対する最もよく知られている解決策は、OAuth2.0とJSONWeb Token(JWT)です。

このJSONWebTokenチュートリアルに入る前に、JWTとは正確には何ですか?

JSON Web Tokenとは何ですか?

JSON Web Tokenは、デジタル署名によって検証および信頼できる情報を送信するために使用されます。 コンパクトでURLセーフなJSONオブジェクトで構成されており、その真正性を検証するために暗号で署名されています。また、ペイロードに機密情報が含まれている場合は暗号化することもできます。

コンパクトな構造のため、JWTは通常HTTP AuthorizationヘッダーまたはURLクエリパラメーターで使用されます。

JSONWebトークンの構造

JWTは、ピリオド文字で区切られたbase64urlでエンコードされた値のシーケンスとして表されます。

laravelとangularjsのJSONWebトークンの例

JWTトークンの例を次に示します。

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0 . yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

ヘッダ

ヘッダーにはトークンのメタデータが含まれ、署名のタイプと暗号化アルゴリズムが最小限に含まれています。 (JSONフォーマッターツールを使用して、JSONオブジェクトをきれいにすることができます。)

ヘッダーの例

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

このJWTサンプルヘッダーは、エンコードされたオブジェクトがJSON Webトークンであり、HMACSHA-256アルゴリズムを使用して署名されていることを宣言します。

これがbase64でエンコードされると、JWTの最初の部分ができます。

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

ペイロード(クレーム)

JWTのコンテキストでは、クレームは、エンティティ(通常はユーザー)に関するステートメント、およびトークン自体に関する追加のメタデータとして定義できます。 クレームには、送信する情報と、サーバーがJSONWebトークン認証を適切に処理するために使用できる情報が含まれています。 私たちが提供できる主張は複数あります。 これらには、登録されたクレーム名、パブリッククレーム名、およびプライベートクレーム名が含まれます。

登録されたJWTクレーム

これらは、IANA JSON WebTokenClaimsレジストリに登録されているクレームです。 これらのJWTクレームは必須ではなく、一連の有用で相互運用可能なクレームの開始点を提供することを目的としています。

これらには以下が含まれます:

  • iss :トークンの発行者
  • sub :トークンの件名
  • aud :トークンのオーディエンス
  • exp :Unix時間で定義されたJWTの有効期限
  • nbf :JWTが処理のために受け入れられてはならない時間を識別する「前ではない」時間
  • iat :トークンが発行されたUnix時間の「発行時」
  • jti :JWT IDクレームは、JWTの一意の識別子を提供します

パブリッククレーム

公の主張には、衝突耐性のある名前を付ける必要があります。 名前をURIまたはURNにすることで、送信者と受信者が閉じたネットワークの一部ではないJWTの名前の衝突を回避できます。

パブリッククレーム名の例としては、 https://www.toptal.com/jwt_claims/is_admin ://www.toptal.com/jwt_claims/is_adminがあります。ベストプラクティスは、その場所にクレームを説明するファイルを配置して、ドキュメントの参照を取り消せるようにすることです。

プライベートクレーム

プライベートクレーム名は、企業内など、既知のシステム間でJWTが閉じた環境でのみ交換される場所で使用できます。 これらは、ユーザーID、ユーザーロール、その他の情報など、自分で定義できるという主張です。

クローズドシステムまたはプライベートシステムの外部で意味が矛盾する可能性のあるクレーム名を使用すると、衝突する可能性があるため、注意して使用してください。

Webトークンをできるだけ小さくしたいので、パブリックおよびプライベートクレーム内で必要なデータのみを使用することに注意することが重要です。

JWTのペイロードの例

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

このペイロードの例には、2つの登録済みクレーム、1つのパブリッククレームと2つのプライベートクレームがあります。 base64でエンコードされると、JWTの2番目の部分ができます。

 eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0

サイン

JWT標準は、JSON Web署名(JWS)仕様に従って、最終的な署名付きトークンを生成します。 これは、エンコードされたJWTヘッダーとエンコードされたJWTペイロードを組み合わせ、HMACSHA-256などの強力な暗号化アルゴリズムを使用して署名することで生成されます。 署名の秘密鍵はサーバーによって保持されているため、既存のトークンを検証して新しいトークンに署名することができます。

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

これにより、JWTの最後の部分が得られます。

 yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

JWTのセキュリティと暗号化

中間者攻撃を防ぐために、TLS/SSLをJWTと組み合わせて使用​​することが重要です。 ほとんどの場合、機密情報が含まれている場合、これでJWTペイロードを暗号化できます。 ただし、保護のレイヤーを追加する場合は、JSON Web Encryption(JWE)仕様を使用してJWTペイロード自体を暗号化できます。

もちろん、JWEを使用することによる追加のオーバーヘッドを回避したい場合、別のオプションは、機密情報をデータベースに保持し、機密データにアクセスする必要があるときはいつでも、サーバーへの追加のAPI呼び出しにトークンを使用することです。

なぜWebトークンが必要なのですか?

JWT認証を使用するすべての利点を確認する前に、過去に認証が行われた方法を確認する必要があります。

サーバーベースの認証

サーバーベースの認証

HTTPプロトコルはステートレスであるため、ユーザー情報を保存するメカニズムと、ログイン後の後続のすべての要求でユーザーを認証する方法が必要です。 ほとんどのWebサイトは、ユーザーのセッションIDを保存するためにCookieを使用します。

使い方

ブラウザは、ユーザーのIDとパスワードを含むPOSTリクエストをサーバーに送信します。 サーバーは、ユーザーのブラウザに設定され、ユーザーを識別するためのセッションIDを含むCookieで応答します。

ユーザーデータはサーバーに保存されるため、後続のすべての要求で、サーバーはそのセッションを見つけて逆シリアル化する必要があります。

サーバーベースの認証の欠点

  • スケーリングが難しい:サーバーは、ユーザーのセッションを作成し、サーバー上のどこかに永続化する必要があります。 これは、メモリまたはデータベースで実行できます。 分散システムを使用している場合は、アプリケーションサーバーに結合されていない別のセッションストレージを使用していることを確認する必要があります。

  • クロスオリジンリクエストシェアリング(CORS) :AJAX呼び出しを使用して別のドメインからリソースをフェッチする場合(「クロスオリジン」)、デフォルトではHTTPリクエストにクロスオリジンのCookieが含まれていないため、禁止されたリクエストで問題が発生する可能性があります。オリジンリクエスト。

  • Webフレームワークとの結合:サーバーベースの認証を使用する場合、フレームワークの認証スキームに関連付けられます。 異なるプログラミング言語で記述された異なるWebフレームワーク間でセッションデータを共有することは、非常に困難であるか、不可能ですらあります。

トークンベースの認証

トークンベースの認証

トークンベース/JWT認証はステートレスであるため、セッションにユーザー情報を保存する必要はありません。 これにより、ユーザーがどこにログインしたかを気にせずにアプリケーションをスケーリングできます。同じトークンを使用して、ログインしているドメイン以外のドメインから安全なリソースを簡単に取得できます。

JSONWebトークンのしくみ

ブラウザまたはモバイルクライアントは、ユーザーログイン情報を含む認証サーバーに要求を行います。 認証サーバーは新しいJWTアクセストークンを生成し、それをクライアントに返します。 制限されたリソースへのすべての要求で、クライアントはクエリ文字列またはAuthorizationヘッダーでアクセストークンを送信します。 次に、サーバーはトークンを検証し、トークンが有効な場合は、安全なリソースをクライアントに返します。

認証サーバーは、任意の安全な署名方法を使用してトークンに署名できます。 たとえば、すべての関係者間で秘密鍵を共有するための安全なチャネルがある場合は、HMACSHA-256などの対称鍵アルゴリズムを使用できます。 または、RSAなどの非対称の公開鍵システムを使用して、さらに鍵を共有する必要をなくすこともできます。

トークンベースの認証の利点

ステートレス、スケーリングが容易:トークンには、ユーザーを識別するためのすべての情報が含まれているため、セッション状態が不要になります。 ロードバランサーを使用すると、ログオンした同じサーバーにバインドされる代わりに、ユーザーを任意のサーバーに渡すことができます。

再利用性:複数のプラットフォームとドメインで実行され、ユーザーの認証に同じトークンを再利用する、多数の個別のサーバーを使用できます。 別のアプリケーションと権限を共有するアプリケーションを簡単に構築できます。

JWTセキュリティ:Cookieを使用していないため、クロスサイトリクエストフォージェリ(CSRF)攻撃から保護する必要はありません。 機密情報をトークンに入れる必要がある場合は、JWEを使用してトークンを暗号化し、中間者攻撃を防ぐためにHTTPS経由でトークンを送信する必要があります。

パフォーマンス:各リクエストでセッションを見つけて逆シリアル化するサーバー側のルックアップはありません。 私たちがしなければならない唯一のことは、HMAC SHA-256を計算してトークンを検証し、その内容を解析することです。

Laravel5とAngularJSを使用したJSONWebトークンの例

このJWTチュートリアルでは、2つの一般的なWebテクノロジー(バックエンドコード用のLaravel 5とフロントエンドシングルページアプリケーション(SPA)の例用のAngularJS)でJSONWebトークンを使用して基本認証を実装する方法を示します。 (ここでデモ全体を見つけることができ、チュートリアルに沿って従うことができるように、このGitHubリポジトリにソースコードがあります。)

このJSONWebトークンの例では、クレームで送信される情報の機密性を確保するために、いかなる種類の暗号化も使用しません。 実際には、TLS / SSLが要求を暗号化するため、これは多くの場合問題ありません。 ただし、トークンにユーザーの社会保障番号などの機密情報が含まれる場合は、JWEを使用して暗号化する必要もあります。

Laravelバックエンドの例

Laravelを使用してユーザー登録を処理し、ユーザーデータをデータベースに永続化し、Angularアプリが使用するために認証が必要な制限付きデータを提供します。 クロスオリジンリソースシェアリング(CORS)もシミュレートするためのサンプルAPIサブドメインを作成します。

インストールとプロジェクトのブートストラップ

Laravelを使用するには、Composerパッケージマネージャーをマシンにインストールする必要があります。 Laravelで開発するときは、VagrantのLaravelHomesteadパッケージ済み「ボックス」を使用することをお勧めします。 オペレーティングシステムに関係なく、完全な開発環境を提供します。

JWT Laravelアプリケーションをブートストラップする最も簡単な方法は、ComposerパッケージのLaravelインストーラーを使用することです。

 composer global require "laravel/installer=~1.1"

これで、 laravel new jwtを実行して、新しいLaravelプロジェクトを作成する準備が整いました。

このプロセスに関する質問については、Laravelの公式ドキュメントを参照してください。

基本的なLaravel5アプリケーションを作成したら、 Homestead.yamlをセットアップする必要があります。これにより、ローカル環境のフォルダーマッピングとドメイン構成が構成されます。

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

vagrant upコマンドでVagrantボックスを起動し、vagrant vagrant sshを使用してログインした後、以前に定義したプロジェクトディレクトリに移動します。 上記の例では、これは/home/vagrant/coding/jwtます。 これで、データベースに必要なユーザーテーブルを作成するために、 php artisan migrateコマンドを実行できます。

Composerの依存関係のインストール

幸い、Laravelに取り組んでいて、アプリケーションを再利用および拡張できる多くの優れたパッケージを維持している開発者のコ​​ミュニティがあります。 この例では、サーバー側でトークンを処理するためにtymon/jwt-authを使用し、Barryvdによるbarryvdh/ barryvdh/laravel-cors -corsを使用します。 Heuvel、CORSを処理するため。

jwt-auth

composer.jsontymon/jwt-authパッケージを要求し、依存関係を更新します。

 composer require tymon/jwt-auth 0.5.*

JWTAuthServiceProviderapp/config/app.phpプロバイダー配列に追加します。

 'Tymon\JWTAuth\Providers\JWTAuthServiceProvider'

次に、 app/config/app.phpファイルのJWTAuth aliasesを追加します。

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

最後に、次のコマンドを使用してパッケージ構成を公開します。php artisan config:publish tymon / jwt-auth

JSON Webトークンは、秘密鍵を使用して暗号化されます。 php artisan jwt:generateコマンドを使用して、そのキーを生成できます。 config/jwt.phpファイル内に配置されます。 ただし、実稼働環境では、構成ファイル内にパスワードやAPIキーを含める必要はありません。 代わりに、それらをサーバー環境変数内に配置し、 env関数を使用して構成ファイルで参照する必要があります。 例えば:

 'secret' => env('JWT_SECRET')

このパッケージとそのすべての構成設定については、Githubで詳しく知ることができます。

laravel-cors

composer.jsonbarryvdh/laravel-corsパッケージを要求し、依存関係を更新します。

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

CorsServiceProviderapp/config/app.phpプロバイダー配列に追加します。

 'Barryvdh\Cors\CorsServiceProvider'

次に、ミドルウェアをapp/Http/Kernel.phpに追加します。

 'Barryvdh\Cors\Middleware\HandleCors'

php artisan vendor:publishコマンドを使用して、構成をローカルのconfig/cors.phpファイルに公開します。

cors.phpファイル構成の例:

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

HTTPリクエストのルーティングと処理

簡潔にするために、Laravelのルーティングとコントローラーへのリクエストの委任を担当するroutes.phpファイル内にすべてのコードを配置します。 通常、すべてのHTTPリクエストを処理するための専用コントローラーを作成し、コードをモジュール化してクリーンに保ちます。

を使用してAngularJSSPAビューをロードします

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

ユーザー登録

ユーザー名とパスワードを使用して/signupPOSTリクエストを行うと、新しいユーザーを作成してデータベースに保存しようとします。 ユーザーが作成された後、JWTが作成され、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')); });

ユーザーサインイン

ユーザー名とパスワードを使用して/signinPOSTリクエストを行うと、ユーザーが存在することを確認し、JSON応答を介してJWTを返します。

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

同じドメインで制限付きリソースを取得する

ユーザーがサインインすると、制限されたリソースを取得できます。 認証されたユーザーを必要とするリソースをシミュレートするルート/restrictedを作成しました。 これを行うには、リクエストAuthorizationヘッダーまたはクエリ文字列がバックエンドが検証するためのJWTを提供する必要があります。

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

この例では、 'before' => 'jwt-auth'を使用してjwt-authパッケージで提供されるjwt-authミドルウェアを使用しています。 このミドルウェアは、リクエストをフィルタリングし、JWTトークンを検証するために使用されます。 トークンが無効、存在しない、または期限切れの場合、ミドルウェアはキャッチできる例外をスローします。

Laravel 5では、 app/Exceptions/Handler.phpファイルを使用して例外をキャッチできます。 render関数を使用して、スローされた例外に基づいてHTTP応答を作成できます。

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

ユーザーが認証され、トークンが有効な場合、JSONを介して制限されたデータをフロントエンドに安全に返すことができます。

APIサブドメインからの制限付きリソースの取得

次のJSONWebトークンの例では、トークンの検証に別のアプローチを採用します。 jwt-authミドルウェアを使用する代わりに、例外を手動で処理します。 APIサーバーapi.jwt.dev/v1/restrictedPOSTリクエストを送信すると、クロスオリジンリクエストが作成され、バックエンドでCORSを有効にする必要があります。 幸い、 config/cors.phpファイルですでにCORSを設定しています。

 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フロントエンドの例

フロントエンドとしてAngularJSを使用しており、ユーザー認証とサンプルデータ用のLaravelバックエンド認証サーバーへのAPI呼び出しと、クロスオリジンのサンプルデータ用のAPIサーバーに依存しています。 プロジェクトのホームページに移動すると、バックエンドはAngularアプリケーションをブートストラップするresources/views/spa.blade.phpビューを提供します。

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

Angularアプリケーションのブートストラップ

spa.blade.phpには、アプリケーションの実行に必要な最低限の要素が含まれています。 スタイリングにはTwitterBootstrapを使用し、Bootswatchのカスタムテーマも使用します。 AJAX呼び出しを行うときに視覚的なフィードバックを得るには、Angular-loading-barスクリプトを使用します。このスクリプトは、XHRリクエストをインターセプトし、ローディングバーを作成します。 ヘッダーセクションには、次のスタイルシートがあります。

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

マークアップのフッターには、ライブラリへの参照と、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>

AngularJS用のngStorageライブラリを使用して、トークンをブラウザーのローカルストレージに保存し、 Authorizationヘッダーを介してリクエストごとにトークンを送信できるようにします。

もちろん、実稼働環境では、パフォーマンスを向上させるために、すべてのスクリプトファイルとスタイルシートを縮小して結合します。

Bootstrapを使用して、ユーザーのサインインステータスに応じて適切なリンクの表示を変更するナビゲーションバーを作成しました。 サインインステータスは、コントローラのスコープ内のtoken変数の存在によって決定されます。

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

ルーティング

すべてのフロントエンドルートの構成を担当するapp.jsという名前のファイルがあります。

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

ここでは、 HomeControllerまたはRestrictedControllerのいずれかによって処理される4つのルートを定義したことがわかります。 すべてのルートは、部分的なHTMLビューに対応しています。 また、バックエンドへのHTTPリクエストのURLを含む2つの定数を定義しました。

インターセプターをリクエストする

AngularJSの$httpサービスを使用すると、バックエンドと通信してHTTPリクエストを行うことができます。 この例では、すべてのHTTPリクエストをインターセプトし、ユーザーが認証されている場合はJWTを含むAuthorizationヘッダーを挿入します。 インターセプターを使用して、グローバルHTTPエラーハンドラーを作成することもできます。 これは、ブラウザのローカルストレージで利用可能な場合にトークンを挿入するインターセプターの例です。

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

コントローラー

controllers.jsファイルで、アプリケーション用にHomeControllerRestrictedControllerの2つのコントローラーを定義しました。 HomeControllerは、サインイン、サインアップ、およびログアウト機能を処理します。 サインインフォームとサインアップフォームからAuthサービスにユーザー名とパスワードのデータを渡します。AuthサービスはHTTPリクエストをバックエンドに送信します。 次に、バックエンドからの応答に応じて、トークンをローカルストレージに保存するか、エラーメッセージを表示します。

 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は同じように動作しますが、 DataサービスでgetApiData getRestrictedDataを使用してデータをフェッチするだけです。

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

バックエンドは、ユーザーが認証されている場合にのみ、制限されたデータを提供する責任があります。 つまり、制限されたデータで応答するには、そのデータのリクエストに、 Authorizationヘッダーまたはクエリ文字列内に有効なJWTが含まれている必要があります。 そうでない場合、サーバーは401Unauthorizedエラーステータスコードで応答します。

認証サービス

Authサービスは、バックエンドへのサインインとHTTPリクエストのサインアップを担当します。 リクエストが成功すると、レスポンスには署名されたトークンが含まれ、base64でデコードされ、同封のトークンクレーム情報がtokenClaims変数に保存されます。 これは、 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; } }; } ]);

データサービス

これは、認証サーバーとAPIサーバーにダミーの制限付きデータを要求する単純なサービスです。 リクエストを行い、成功とエラーのコールバックをコントローラーに委任します。

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

このJSONWebTokenチュートリアルを超えて

トークンベースの認証により、特定の認証スキームに関連付けられていない分離されたシステムを構築できます。 トークンはどこでも生成され、トークンの署名に同じ秘密鍵を使用するシステムで消費される可能性があります。 モバイル対応であり、Cookieを使用する必要はありません。

JSON Web Tokenは、一般的なすべてのプログラミング言語で機能し、急速に人気が高まっています。 Google、Microsoft、Zendeskなどの企業が支援しています。 インターネット技術特別調査委員会(IETF)による標準仕様はまだドラフト版であり、将来わずかに変更される可能性があります。

JWTについては、セキュリティの詳細の処理方法や、有効期限が切れたときのトークンの更新など、まだ多くのことをカバーする必要がありますが、JSON Web Tokenチュートリアルでは、基本的な使用法と、さらに重要なことに、JWTを使用する利点を示す必要があります。

Toptal Engineeringブログでさらに読む:

  • Node.js / TypeScript REST APIの構築、パート3:MongoDB、認証、および自動テスト