こちらはAlgoliaのJulien Bourdeau(@julienbourdeau)が書いた API keys vs JWT authorization – Which is best? の翻訳記事です。
あなたがご自身のAPIを構築する際には、ユースケースを検討していきながら、APIにどのようなセキュリティ手法を実装するか決めていくことになります。あるユースケースでは、APIキーで十分かもしれませんが、他のユースケースでは、JSON Web Tokens (JWT/ジョット)認証で得られるような保護と柔軟性が必要になるかもしれません。つまり、APIキーとJWT認証の比較においては、勝者は「場合による(it depends)」ということになります。
すべてのAPIコールには、ある種のセキュリティとアクセスコントロールが必要になります。適切なACLを備えたAPIキーは、オーバーヘッドをあまり増やさずに十分なセキュリティを提供することができます。しかし、多かれ少なかれほとんどすべてのタスクにマイクロサービスが使用されるようになったことで、APIエコシステムには、JWT認証のような、より統一された、粒度の高い、安全な方法が必要となるかもしれません。
APIキーで問題ないケース
AlgoliaのようなCloudベースの検索APIを使用するようなオンラインビジネスにおいては、インデックスされているデータに機密性の高いデータが含まれていない場合、通常は読み取り専用(read-only)なAPIキーを用いることで大きなリスクはないでしょう。クライアントサイドのアプリケーションは、APIキーを使って直接クラウド上の検索エンジンに接続することで、より良いパフォーマンスを得ることができます(バックエンドサーバーを介するとネットワークのラウンドトリップ等に時間がかかってしまいます)。一方で、インデックスのデータの更新にはアクセス制限のあるAPIキーが必要であり、これは決してパブリックに公開してはいけません。
しかし、このどちらのユースケース(検索とインデックスデータの更新)においても、APIキーを使うことに問題はないと言えます。つまり、JWT認証のオーバーヘッドが早急に必要となることはありません。
JWT認証を検討する時
一般的には、APIには以前にも増して柔軟性と保護が求められるようになってきていると言えます。JWT認証は、追加のセキュリティレベルを提供するだけでなく(こちらには後でより詳しく触れます)、日々活用される多くのAPIやネットワークを調和させるため、より管理しやすく、簡単な方法を提供します。次のセクションで説明するように、JWTはユーザーおよびアプリケーションレベルの(暗号化もしくはハッシュ化された)情報を含む単一の共有トークンを生成することで、認証および認可を一元化し、同じエコシステム内のすべてのAPIがトークン保持者に何ができるかを判断できるようにします。
APIキーは一見とてもシンプルで、適切なAPIキーを送信するだけで全てが解決されるように思いますが、それは少し短絡的な見方であるかもしれません。そのエコシステムが多くの統合されたマイクロサービスに依存しているような場合、数多くのAPIキーをやりくりすることは面倒であり、信頼性の低下を招き、ほとんど管理していくことが不可能になってしまいます。APIキーの数が増え、変更され、有効期間が切れ、削除され、ACLが変更されたとしても、同じAPIキーに依存しているアプリケーションやユーザーには通知されないまま、更に様々なことが起こります。
こういった事態において、JWTを使用すればシングルサインオンアーキテクチャの礎を築くことができます。これについては、 JWTへの切り替え のセクションで後述します。
APIキー vs JWT認証
APIキーを使う
APIキーは直感的で、シンプル、そして完全にトランスペアレントです。そこにある情報に関する何かを表すわけでもなければ、秘密のメッセージを暗号化するわけでもありません。それらはシンプルに(人間にとって)リーダブルではないユニークなIDです。
ここで、クライアントサイドのJavaScriptで、パブリックなAPIキーを使用した例をご紹介します。このコードにはApp ID (app-id-BBRSSHR
)とAPIキー(temp-search-key-ere452sdaz56qsjh565d
)が含まれており、これによって検索ができるようになります。App IDはユーザー向けのアプリケーション(オンラインのWebサイトやストリーミングサービス等)の1つを指します。APIキーは、不必要な悪用などからの保護をするため、テンポラリで生存期間の短い(一定期間で期限が切れる)ものとします。
import { hitTemplate } from "./helpers";
const search = instantsearch({
appId: "app-id-BBRSSHR",
apiKey: "temp-search-key-ere452sdaz56qsjh565d",
indexName: "demo_ecommerce"
});
もう一つの例としては、より安全な形でAPIキーを扱う必要のあるインデクシングが挙げられます。こちらは同じフォーマット(App ID + APIキー)ですが、プライベートなものであり、バックエンドでコンパイルされたコードの中に入れたり、セキュアなデータベースなどで管理されている必要があります。こちらの例でのAPP ID(YourApplicationID
)はバックオフィスのシステムを指し、APIキー(YourAdminAPIKey
)はメンテナンスを容易にするために、年に一度だけ変更するパーマネントなadminキーになるかもしれません。
use Algolia\AlgoliaSearch\SearchClient;
$client = SearchClient::create(
'YourApplicationID',
'YourAdminAPIKey'
);
$index = $client->initIndex('demo_ecommerce');
$index->saveObject(
[
'firstname' => 'Jimmie',
'lastname' => 'Barninger',
'city' => 'New York',
'objectID' => 'myID'
]
);
JWTトークンを使う
JWTトークンはシグネチャー(著名)や暗号化アルゴリズムによってマスクされたhidden(隠された)でエンコードされた情報を含む大きなリーダブルではないキャラクターの集合です。ヘッダー、ボディ、シグネチャーの3つの部分から構成されていて、それらはピリオドで区切られています: Header.Body.Signature
という形式です。
EZPZAAdsqfqfzeezarEUARLEA.sqfdqsTIYfddhtreujhgGSFJ.fkdlsqEgfdsgkerGAFSLEvdslmgIegeVDEzefsqd
JWTヘッダーはEZPZAAdsqfqfzeezarEUARLEA
の部分で、ここには以下の情報が含まれています:
{
"alg": "HS256",
"typ": "JWT"
}
ここにはRS256やHS256といった様々なアルゴリズムを適用することができ、シグネチャーを生成する再に秘密鍵が必要なHS256を使います。RS256は秘密鍵と公開鍵の組み合わせを使います。
JWTボディー(ペイロードと呼ばれます)はsqfdqsTIYfddhtreujhgGSFJ
の部分で、そのトークンのユーザーの権利を確立するのに役に立つユーザーのアイデンティティが含まれています。そして、有効期間(より短い方が安全)といった情報も含まれます。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
シグネチャー(署名)はfkdlsqEgfdsgkerGAFSLEvdslmgIegeVDEzefsqd
の部分で、ヘッダーで示されているように、HS256によるハッシュ化する方法を使って、ヘッダー、ボディー、共有の秘密鍵を組み合わせて生成されます。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
このようにして、Header.Body.Signatureというトークンが出来上がります。
認証と認可について
APIキーとJWTはどちらも認証と認可のために使われますが、その方法は異なります。
- 認証によってユーザーまたはアプリケーションは、APIの1つまたは複数のメソッドを使うことが許可されます
- 認可は、それらのメソッドをどのように使えるのかを定義します。例えば、あるアプリやユーザーはデータを読むことだけできて; あるユーザーは更新ができ; あるユーザーは管理者(ロールとパーミッション)です。APIキーも同様に、ACLSによって管理され、read-only, write-access, もしくは admin のいずれかになることができます
APIキーは、同じAPIキーを使って認証と認可を行います。JWT認証は、認証トークンを生成する前に、最初の認証プロセスが必要になります。トークンが生成されると、エコシステム全体においてそのトークンの所有者が何ができて何ができないかを決定するため使われます。
さらに、APIキーはユーザーではなくアプリケーションを認証するのに対し、JWTはユーザーとアプリケーションの両方を認証します。もちろん、APIキーをユーザーレベルの認証に使うこともできますが、そのためにうまく設計されているとは言えません。エコシステムの世界では、ユーザーやセッションIDごとにAPIキーを生成して管理する必要がありますが、これはシステムにとって不必要な負担と言えるでしょう。
より良い保護とセキュリティの強化
セキュリティの観点では、APIキーもJWTも攻撃の対象になります。最善のセキュリティ対策は、すべてのエンドツーエンド通信にセキュアなアーキテクチャを実装することです。
しかし、APIキーは隠蔽されることに依存しているため、歴史的に見ても安全性は低いことは否めません。SSL/TLS/HTTPSでキーを隠したり、バックエンドの処理に利用を制限したりすることができますが、すべてのAPIの使用をコントロールすることはできません。APIキーは漏洩する可能性が高く、HTTPSが常に可能であるとは限りません。JWTの場合、トークンはハッシュ化/暗号化されているため、よりセキュアなメソドロジがそこにはあり、漏洩の可能性は低いと言えます。
JWTトークンにはどのような情報が含まれる?
APIキーとJWTトークンにおける最も顕著な違いは、JWTトークンが自己完結しているということでしょう。トークンには、APIが取引を保護し、トークン保持者のきめ細かい権限を決定するために必要な情報が含まれています。対照的に、APIキーはその一意性を使って最初のアクセスを得るわけですが、その後、APIはキーの関連するACLをセントラルテーブルの中に見つけて、そのキーが何にアクセスできるかを正確に決定する必要があります。一般的に、APIキーはアプリケーションレベルのセキュリティのみを提供し、すべてのユーザーに同じアクセスを与えますが、JWTトークンはユーザーレベルのアクセスを提供します。
JWTトークンは、有効期間やユーザー識別子などの情報を含めることができ、エコシステム全体におけるユーザーの権限を決定することができます。
それでは、JWTトークンに含めることができるいくつかの情報を見てみましょう。
iss (issuer): identifies the principal that issued the JWT.
sub (subject): identifies the principal that is the subject of the JWT. Must be unique
aud (audience): identifies the recipients that the JWT is intended for (array of strings/uri)
exp (expiration time): identifies the expiration time (UTC Unix) after which you must no longer accept this token. It should be after the issued-at time.
nbf(not before): identifies the UTC Unix time before which the JWT must not be accepted
iat (issued at): identifies the UTC Unix time at which the JWT was issued
jti (JWT ID): provides a unique identifier for the JWT.
例
{
"iss": "stackoverflow",
"sub": "joe",
"aud": ["all"],
"iat": 1300819370,
"exp": 1300819380,
"jti": "3F2504E0-4F89-11D3-9A0C-0305E82C3301",
"context":
{
"user":
{
"key": "joe",
"displayName": "Joe Smith"
},
"roles":["admin","finaluser"]
}
}
JWT認証は柔軟性、信頼性、より高い安全性を提供
たくさんのアプリケーションがある場合のシナリオ:
- 全ユーザーのAPIの利用状況を把握することを可能にするアプリケーション
- 課金情報や顧客データへのアクセスを提供するアプリケーション
- APIユーザーが異なるシステムの設定を変更することができるアプリケーション
- 製品データやビジネスコンテンツを抽出するアプリケーション
- その他
APIキーで行う場合
問題が発覚するのはAPIが増えたときです。数多くのAPIにアクセスするのに必要な100個以上のキーをどこに保存するのか?そして、数多くのAPIキーを管理するには、エコシステム内で動作する全てのアプリケーションが利用できるようなAPIキーのテーブルを作成して運用する必要があります。
つまり、エコシステム内の全てのアプリケーションはデータベースを認識する必要があるということです。各アプリケーションはそのテーブルに接続して、そこからデータを読み取る必要があります。そしていくつかのアプリケーションは新しいキーを生成したり、既存のキーを変更したりすることを許可する必要性が出てきます。これらが悪いことなわけではありませんが、維持していくことは難しくなります。これを管理する一つの方法として、このデータベースに対してキーを検証するAPIを作成することですが、それを行おうとすると、今度はこのAPIに接続するための2つ目の認証システムが必要になってしまいます。
APIキーの取得が面倒なだけでなく、その期間や認証レベルを維持するのも面倒になるでしょう。また、すべてのアプリケーションがアプリレベルで機能するわけではなく、ユーザーレベルの権限が必要になってきます。さらに、APIはシステムごとに異なる方法で使用されることもあります。さらに悪いことに、異なるアプリケーションがAPIキーを共有するため、共有されたAPIキーの正しいアクセスレベルを維持するために、異なるアプリケーションに依存することになってしまいます。
JWTへの切り替え
認証が必要なAPIは、どのようなものでも簡単にJWTの認証に切り替えることができます。JWTの認証では、ユーザーベースの認証もカバーされます。ユーザーが認証されると、そのユーザーは安全なトークンを取得して、すべてのシステムで使用できるようになります。ユーザー(トークン)の管理は一元化されています。アクセス権限を設定して、各ユーザーにシステムごとに異なる権限を与える。JWT認証のエンドポイントは、ユーザーを認証し、トークンを生成します。
このアーキテクチャでは、次のステップとして、エコシステム全体へのシングルサインオンを作成して、権限のために依存するのはトークンのみという状況にすることができます。最終的に、最もシンプルで堅牢かつ管理しやすいアプローチは、認証と認可の両方を行う専用のシングルエンドポイントを作って、エコシステム全体の他のすべてのサーバーが、クライアントとサーバー間のAPIインタラクションを認可するためにそのセントラルポイントに依存できるようにする、ということです。
まとめ: JWTは必要な場合もあれば、やり過ぎな場合もある
アプリケーション開発者としてのAPIを構築する際の最大の関心事は、それが適切に使われていて、正しいデータと機能を提供できているかということです。そのため、私は、最適で最も管理しやすいセキュリティを提案するDevOpsのアドバイスに大きく依存しています。しかし、問題はセキュリティだけではありません。大規模なエコシステムにおいては、複数のシステムやサーバーにまたがるマイクロサービスにアクセスするためのシンプルで堅牢な方法が必要なわけですが、それには認証と認可のプロセスを一元化することが最適です。
JWTは以下のような場合はやり過ぎになる
- 数個のAPIで構成されるようなシンプルなエコシステム
- サードパーティーAPIの提供者
サードパーティーのAPIは、簡単に使えて、素早く実装できて、インテグレーションの際の作業が少ないものである必要があります。さらに、トークンに署名するために使用するキーを共有する必要があります。そのため、両方のエンドをコントロールできるのがベストですが、サードパーティーの場合、そのようなことはあり得ないでしょう。また、実装を信頼する必要があるわけですが、例えば、APIは有効期間やNBF(“not before”)を無視することを選択することもできてしまうわけです。
JWTがやり過ぎではない場合…
複数のサービスが多数のネットワーク上で通信する必要がある場合は、JWTを使いたくなるでしょう。それらのやりとりを一元化して、セキュリティを確保することは非常に重要です。特に、各ネットワークやアプリケーションが、アプリケーションだけでなくユーザーに応じて異なるレベルのアクセスを必要とする場合に重要となります。また、トラフィックを制御して、ネットワークの呼び出しに優先順位を付けられるようにすることも重要です。最後に、新しいマイクロサービスを追加したり、既存のマイクロサービスを改善したりするときに、シンプルでプラグアンドプレイ(抜き差しするだけで良い)な体験が必要です。
コメント