トークンベースの認証
トークンは、それ自体では意味や用途を持たないデータですが、正しいトークン化システムと組み合わせると、アプリケーションを保護する上で重要な役割を果たします。トークンベースの認証の場合、サーバーへの各リクエストに署名付きトークンを確実に付け、サーバーがその信頼性を検証した時のみ、そのリクエストに応答します。
JSON Web Token (JWT) はオープン標準 ( RFC 7519 ) であり、JSONオブジェクトとしてエンコードされたパーティ間で情報を安全に送信するための、コンパクトで自己完結型の方法を定義します。JWTは、クエリ文字列、ヘッダー、およびPOSTリクエストの本文内でトークンを簡単に送信できる、コンパクトなサイズのため広く普及しています。
なぜトークンを使うのか?
トークンの使用には、Cookieなどの従来の方法と比較して多くの利点があります。
- トークンはステートレスです。トークンは自己完結型で、認証に必要なすべての情報が含まれています。サーバーがセッション状態を保存する必要がなくなるため、スケーラビリティに優れています。
- トークンはどこからでも生成できます。トークンの生成はトークンの検証から切り離されているため、トークンの署名を別のサーバーで処理したり、Auth0などの別の会社を介して処理したりすることもできます。
- きめ細かいアクセス制御。トークンのペイロード内で、ユーザーの役割と権限、およびユーザーがアクセスできるリソースを簡単に指定できます。
これらは、JSON Web Token (JWT) が提供する利点のほんの一部です。詳細については、認証を管理するためのトークンとCookieを比較してより深く検討した、こちらのブログ投稿をご覧ください。
JSON Web Token (JWT) の構造
JSON Web Token (JWT) は、次の 3 つの部分から構成されます。ヘッダー、ペイロード、および署名。ヘッダーとペイロードはBase64でエンコードされ、ピリオドで連結され、最後に結果がアルゴリズムで署名され、ヘッダー.クレーム.署名の形式でトークンが生成されます。ヘッダーは、トークンのタイプと、トークンの署名に使用された、トークンの種類とハッシュアルゴリズムを含むメタデータで構成されています。ペイロードには、トークンがエンコードしているクレームデータが含まれています。最終的に次のようになります。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtZXNzYWdlIjoiSldUIFJ1bGVzISIsImlhdCI6MTQ1OTQ0ODExOSwiZXhwIjoxNDU5NDU0NTE5fQ.-yIVBD5b73C75osbmwwshQNRC7frWUYrqaTjTpza2y4
トークンは操作から保護するため署名されています。暗号化されてはいません。つまり、トークンを簡単にデコードしてその内容を明らかにできるということです。jwt.ioに移動して上記のトークンを貼り付けると、ヘッダーとペイロードを読み取ることができますが、正しいシークレットがなければトークンは役に立たず、「無効な署名」というメッセージが表示されます。正しいシークレット (この例では文字列 L3@RNJWT
) を追加すると、「署名が検証されました」というメッセージが表示されます。
実際の状況では、クライアントがサーバーにリクエストを送信し、リクエストとともにトークンを渡します。サーバーはトークンの検証を試み、成功した場合はリクエストの処理を続行します。サーバーがトークンを検証できなかった場合、サーバーは401エラー
を送信し、承認を検証できなかったため、要求を処理できなかったというメッセージを送信します。
JSON Web Token (JWT) のベストプラクティス
実際にJWTを実装する前に、トークンベースの認証がアプリケーションに適切に実装されていることを確認するための、いくつかのベストプラクティスについて説明しましょう。
- 秘密を守って、安全を守る。署名キーは他の資格情報と同様に扱い、絶対に必要とするサービスにのみ開示します。
- 機密データをペイロードに追加しないでください。トークンは操作から保護するために署名されており、簡単にデコードされます。最高のパフォーマンスとセキュリティを得るために、ペイロードに最小限の数のクレームを追加します。
- トークンに有効期限を設定します。厳密には、いったんトークンが署名されると、署名キーが変更されるか、有効期限が明確に設定されない限り永久に有効です。これによって潜在的な問題起きる可能性があるため、トークンの有効期限の設定またはトークンの取り消し、あるいはその両方を行う戦略を立ててください。
- HTTPSを採用します。HTTPS以外の接続でトークンを送信しないでください。そのような要求は傍受される可能性があり、トークンが危険にさらされます。
- すべての承認のユースケースを検討してください。たとえば、トークンがサーバーから生成されたことを確認する、セカンダリトークン検証システムを追加することは一般的ではありませんが、要件を満たすために必要な場合があります。
詳細とベストプラクティスについては、「トークンについて知っておくべき10項目」のブログ投稿をご覧ください。
トークンベースで認証の簡素化
トークンベースの認証とJWTは広くサポートされています。JavaScript、Python、C#、Java、PHP、Ruby、Goなどには、JSON web token (JWT) に簡単に署名して検証するためのライブラリがあります。API を実装して、JWTでどれだけ迅速に保護できるか見てみましょう。
当社がNode.js を使用してAPIを構築することを選択したのは、セットアップが最小限で済むためです。JWTの実装のコードを見てみましょう。
// 依存関係を読み込む
var express = require('express');
var jwt = require('jsonwebtoken');
var app = express();
// ウェルカムメッセージを表示するホームルートを登録する
// このルートはトークンなしでアクセスできます
app.get('/', function(req, res){
res.send('Welcome to our API');
})
// ルートを登録して新しいトークンを取得する
// 実際の状況では、ユーザー資格情報を認証します
// トークンを作成する前ですが、簡単にするためにこのルートにアクセスします
// 2分間有効な新しいトークンを生成します
app.get('/token', function(req, res){
var token = jwt.sign({username:'ado'}, 'supersecret',{expiresIn:120});
res.send(token)
})
// データを表示するために有効なトークンを必要とするルートを登録する
app.get('/api', function(req, res){
var token = req.query.token;
jwt.verify(token, 'supersecret', function(err, decoded){
if(!err){
var secrets = {'accountNumber' : '938291239','pin' : '11289','account' : 'Finance'};
res.json(secrets);
} else {
res.send(err);
}
})
})
// ポート3000でアプリを起動します
app.listen('3000');
現在のAPIをテストするために、アプリケーションを実行して localhost:3000
に移動しましょう。「APIへようこそ」というメッセージだけが表示されます。 次に、localhost:3000/api
ルートに移動すると、トークンを取得できなかったことを示すJWTエラー メッセージが表示されます。localhost:3000/token
ルートに移動すると、新しいトークンが生成されていることがわかります。このトークンをコピーし、localhost:3000/api?token={ADD-COPIED-TOKEN-HERE}
に移動すると、会社の金融口座が表示されます。これは意図した応答です。
ほんの数行のコードで、APIエンドポイントを保護することができました。トークンを生成する前の適切なユーザー認証の処理については触れませんでした。それはこれから、Auth0を使って行います。
Auth0によるJWT認証
Auth0を使用した認証フローを示すために、コードに若干の変更を加える必要があります。変更された点をみてみましょう。
// 依存関係を読み込む
var express = require('express');
var jwt = require('express-jwt');
var jwtCheck = jwt({
secret: new Buffer('{YOUR-APP-SECRET}', 'base64'),
audience: '{YOUR-APP-CLIENT-ID}'
});
var app = express();
// コントローラー内でトークンをチェックするのではなく
// ミドルウェアを使用するので、トークンが無効な場合は
// リクエストをさらに実行することを停止します
app.use('/api', jwtCheck);
app.get('/', function(req, res){
res.send('Welcome to our API');
})
app.get('/api', function(req, res){
var secrets = {'accountNumber' : '938291239','pin' : '11289','account' : 'Finance'};
res.json(secrets);
})
app.listen('3000');
これが機能することをテストするために、サーバーを起動して localhost:3000/api
に移動しましょう。認証トークンが送信されていないというメッセージが表示されます。Auth0プレイグラウンドに進み、資格情報を追加してトークンを取得しましょう。次のコードをプレイグラウンドに追加します。
var domain = '{YOUR-AUTH0-DOMAIN}.auth0.com';
var clientID = '{YOUR-APP-CLIENT-ID}';
var lock = new Auth0Lock(clientID, domain);
lock.show({
focusInput: false,
popup: true,
}, function (err, profile, token) {
alert(token)
});
トークンを確実に取得するには、Auth0ダッシュボードのアプリ設定に移動し、https://auth0.github.io/playground
を、許可されたコールバックURLのリストに 追加する必要があります。Auth0プレイグラウンドにログインするか、またはアカウントを作成しみましょう。トークンを示すポップアップが表示されます。
トークンの内容を確認するには、 jwt.io でデコードします。トークンを検証するには、Auth0アプリのクライアントシークレット
が必要で、シークレットbase64エンコードのボックスにチェック印を入れる必要があります。これを行うと、「署名が検証されました」というメッセージが表示されるはずです。
当社のAPIがこのトークンで作動することをテストするには、localhost:3000/api
に対して GET
要求を行い、Authorizationヘッダーでトークンを送信する必要があります。これを行う最も簡単な方法は、APIエンドポイントのテストを簡素化する Postmanなどのアプリを使用することです。呼び出しを行うときに 認証ヘッダーを追加し、値にBearer {TOKEN}
を追加します。呼び出しが行われると、jwtCheck
ミドルウェアがリクエストを調べ、認証ヘッダーが正しい形式であることを確認し、トークンを抽出して検証し、検証された場合は残りのリクエストを処理します。JWT の機能を紹介するためにデフォルト設定のみを使用しましたが、ドキュメントでさらに多くのことを学ぶことができます。
トークンベースの認証のユースケース
JWT認証を実装してAPIを保護することが、いかに簡単かをご紹介しました。最後に、トークンベースの認証が最も適しているユースケースを見てみましょう。
- Platform-as-a-Service (PaaS) アプリケーション – さまざまなフレームワークやクライアントによって使用されている、RESTful APIを公開します。
- モバイルアプリ – サービスとやり取りをする、ネイティブまたはハイブリッドモバイル アプリを実装します。
- シングルページアプリケーション (SPA) – Angular や Reactなどのフレームワークを使用して、最新のアプリケーションを構築します。
JSON Web Token (JWT) 使用については、こちらの投稿で、さらに多くのリソースをご覧ください。