このビデオは、2020 年 4 月 22 日に開催した弊社勉強会のプレゼンテーション録画のパート 4 です。 識別子型・内包型それぞれのアクセストークンの失効と、RFC 7009 (OAuth 2.0 Token Revocation) について、 Authlete の川崎貴彦がお話しします。
川崎: 次に、アクセストークンの失効、無効化する話です。
Expire する前にアクセストークンを無効にしたい、 識別子型アクセストークンを失効させる方法はかんたんです。 認可サーバー側にあるデータベースの中から該当レコードを削除すれば、 そのアクセストークンが存在しないことになります。 この処理だけで済みます。
一方、内包型アクセストークンの失効は、けっこう難しい話です。 PKI の CRL や OCSP 相当のしくみを運用しないと失効はできません。
CRL や OCSP とはなにか。 CRL は Certificate Revocation List です。 期限切れ前に失効した証明書群のシリアルナンバー一覧です。 こういうリストがあります。 この方法の難点は、リストがとても大きくなってしまうことです。 そのリストの同期を取る方法とタイミングをどうするか、 みたいな問題が起こってきます。
あと OCSP という方法もあります。 これは Online Certificate Status Protocol です。 これは単一の証明書の失効状態をリアルタイムに問い合わせるしくみです。 今この証明書があるんだけど、 この証明書は取り消されてないですか、どうですか、 と一個一個問い合わせることが可能な API を提供するものです。 この難点は、通信が起こるのでパフォーマンスの問題が発生します。 また、新たな単一障害点となります。
アクセストークンのうち、JWT 型・内包型のアクセストークンにおいて、 失効機能を実現したい場合、 CRL や OCSP 相当のしくみを、 構築しなくてはなりません。
そのためにはまず、アクセストークンを一意に識別できなければなりません。 これは JWT 内の jti クレームを使えば実現可能です。 逆にいうと、 jti クレームを含めずに JWT 型アクセストークンを発行してしまうと、 そのアクセストークンを失効する方法が無くなってしまいます。 いざアクセストークンを失効させたい場合に大問題になります。
実は、某銀行が過去にある会社のソリューションを採用して、 後日アクセストークンが失効できないと大騒ぎになったことがありまして、 その話を聞いたときに、ああ、きっとそれは JWT 型アクセストークンで、 jti を含んでいないアクセストークンですね、 とピンときました。
まず、そのアクセストークンを一意に識別できる方法を作った後、 次にこの アクセストークンを失効させるために、 その失効させたアクセストークンの一意識別子を、 失効されたアクセストークンのリストに 追加する処理をしていきます。
当該アクセストークンの、 元々の有効期限が切れるまでの間は、 そのリスト内に保持しておく必要があります。
期限が来る前にリストから識別子を削除してしまうと、 ゾンビのように、にそのアクセストークンが復活してしまいます。 一回無効化したんだけど、リストから削除されてしまうと、 あ、有効なんだ、ってことになってしまいます。
このようなしくみをつくった上で、 リソースサーバーは、受け取ったアクセストークンの失効状態を確認します。
確認方法ですけど、 CRL 相当のしくみを運用するのであれば、 失効したアクセストークンのリストを取得して、 その中にアクセストークンの一意識別子が 含まれていないかどうかを確認します。
OCSP 相当のしくみを運用する場合は、 OCSP レスポンダの働きに相当する、 アクセストークン失効状態 API に問い合わせ、となります。
OCSP 相当のしくみを図にすると、こんな感じです。 認可サーバーにアクセストークンの失効状態を返す API を用意して、 リソースサーバーがアクセストークンの中から、 アクセストークンの一意識別子を抜き出して、 この識別子を使って、 アクセストークンの失効状態を返す API に問い合わせをします。 そして、その API が失効状態を返します。 このような感じで、 失効状態を取得することができます。
ただ、失効状態の確認のために、 認可サーバーに問い合わせるとすると、 識別子型アクセストークンにおいて、 イントロスペクション APIに問い合わせするのと同様に、 ネットワーク通信が発生します。 なので、内包型アクセストークンの最大の利点が損なわれます。
これを図にするとこんな感じです。 右のほうはイントロスペクション API の問い合わせです。 リソースサーバーがアクセストークンを持っていて、 認可サーバーのイントロスペクション API に問い合わせて、 アクセストークンの情報をゲットします。
構造は左の方も似ていて、 リソースサーバーはアクセストークンを持っていて、 認可サーバーの失効状態を返す API に問い合わせて、 失効状態をゲットします。 これも似たような感じの通信が発生しています。
というか、イントロスペクションレスポンス内の、active だけに注目すると、 イントロスペクション API は、 アクセストークンの失効状態を返す API そのものである、とわかります。
失効状態を問い合わせる時点で、 識別子型アクセストークンの利点が多いことを考慮すると、 失効状態の問い合わせをしているようでは、 内包型アクセストークンを積極的に採用する理由は、 ほとんど無くなってしまいます。
大きな利点が失われてしまいますので。 内包型アクセストークンを採用する場合は、 方針としてアクセストークンの有効期限を短くして、 失効自体をあきらめる妥協をしない限り、 内包型アクセストークンを採用する利点がほとんどありません。
アクセストークンの有効期間を短くして、と書きました。 なぜ短くしなくてはいけないかというと、 失効をあきらめてしまうので、 なにか問題が起こったときにアクセストークンの無効化ができません。
そうすると、あまりに長い期間有効なアクセストークンを発行すると危険です。 なので、アクセストークンの有効期間を短くして、 失効をあきらめる、という手段を取るしかありません。 このような状態です。
これを差し置いても、 内包型のアクセストークンを採用する理由があるとすれば、 それは、何らかの事情により リソースサーバーと認可サーバーが 通信できない場合です。 こういう場合は、内包型アクセストークンを生成して、 リソースサーバーに渡すしかありません。
いま、アクセストークンの失効の話をしました。 アクセストークンを失効させるための API として、 標準が存在します。 RFC 7009 (OAuth 2.0 Token Revocation) という仕様です。
エンドポイントに対するリクエストはこのような感じです。 token と token_type_hint というリクエストパラメーターがあります。 これはさきほどのイントロスペクションエンドポイントと同じ、 リクエストパラメーターです。
token リクエストパラメーターに、 アクセストークンかリフレッシュトークンを指定して、 そのトークンを revoke します。 token_type_hint リクエストパラメーターが、 ただのヒントに過ぎないのは、 イントロスペクションエンドポイントの仕様と一緒です。
これが revoke の、 サンプルリクエストです。 POST リクエストで、token イコールなんたら、と、 失効したいトークンを指定しています。
この例には Authorization ヘッダーがあります。 クライアント認証が必要なので、Basic の方法でやる場合は、 こういうかたちでクライアント認証をします。
リボケーションレスポンスですが、 渡されたトークンがすでに存在しないとか、 すでに無効になっているいう場合でも、 リボケーションレスポンスは常に OK を返します。 これは、トークンが不正でもエラーにはなりません。 200 OK が返ってきます。
このトークンのチェックの前に、 例えばクライアント認証で失敗した場合には、 別のエラーコードが返ってきますが、 トークン自体のエラーはこういう感じです。
また、このリボケーションの仕様書に、 JSONP をサポートする仕様もちょっと含まれています。 追加のリクエストパラメーターとして、callback があります。
これを指定してリボケーションリクエストを投げると、 図のように、callback がパラメーターとして渡ります。 このリボケーションの処理が成功した場合は、 この package.myCallback という感じの、 JavaScript 関数が呼ばれるコードが返ってきます。 エラー応答の場合には、そのコールバックに、 エラーを表す JSON が 入ったりします。
これはちょっとマイナーですが、 リボケーションの仕様に書かれている JSONP のサポートの仕様です。