IDトークンやアクセストークンのなりすまし防止策(With ASP.NET CORE6)

概要

IDトークン(id_token)やアクセストークン(access_token)は、トークンが発行された=認証されたということになるため、トークンを偽造されたり、不正に横取りされてしまうと、なりすましなどの悪用ができてしまいます。
そのためIDトークンやアクセストークンには偽造防止やなりすましの防止策が組み込まれています。

id_tokenは基本的にWebサーバとOpenIdConnect(OIDC)サーバ間のやりとりなため、なりすましや改ざんをしにくいように見えますが、
OIDCサーバがid_tokenを返却する際はOIDCサーバからWebサーバにリダイレクトする形で返却します。
つまり、Webサーバのリダイレクト用のURLに直接アクセスしてid_tokenを送信すれば改ざんできることになります。
そのためWebサーバ側では受け取ったid_tokenをチェックする必要があります。
どのチェックをどのタイミングで行うかは、サーバサイドでのOIDCシーケンスブラウザサイドでのOIDCシーケンスをご参照ください。
また実際のソースは「テスト用のOIDCサーバを自作する」をご参照ください。

ASP.NET COREのOIDCミドルウェアの場合、下記のチェックはすべて行うことができます。

stateチェック –クライアント(ブラウザやWEBサーバ)の要求とOIDCサーバからの返却の整合性チェック

【code or id_token要求時にチェック】
リダイレクトしてきたのが本当にOIDCサーバだったのかをチェックします。
Webサーバは、stateというランダムな文字列を生成して保持しておきます。
Webサーバは、id_tokenを要求する際にstateをOIDCサーバに送信します。
OIDCサーバはid_tokenをWebサーバにリダイレクトする際にstateも一緒に送信します。
Webサーバは保持しているstateとリダイレクトで返却されたstateが一致することをチェックすることでリダイレクトしてきているのがOIDCサーバだと判断します。

nonceチェック –横取り防止チェック

【id_token要求時にチェック】
Webサーバ側で2回同じid_tokenのリダイレクトを受け取らないようにする仕組みです。
悪意のある人がOIDCサーバからWebサーバにリダイレクトされたid_tokenを傍受したとします。
傍受してもid_tokenのリダイレクトがなくなるわけではないので、Webサーバにid_tokenは渡されます。
この状態で悪意のある人が、傍受したid_tokenをWebサーバにリダイレクトします。
送信したid_tokenは正規のものなので、Webサーバは正常なid_tokenとして処理し、認証OKのCookieを作成して悪意のある人のブラウザに送信します。
すると悪意のある人は認証状態になるため、色々な操作ができてしまいます。
そうならないように、Webサーバはid_token要求時にリクエストにnonceを含めます。
nonceはランダムで重複しにくい値で、生成後はcookie等に保存しておきます。
OIDCサーバは受け取ったnonceをそのまま(特にチェックすることなく)id_tokenに含めます。
OIDCサーバからid_tokenがリダイレクトされた際に、Webサーバはcookie等に保持していたnonceとid_tokenのnonceを比較して一致していたらid_tokenを処理し、cookie等に保存していたnonceを削除します。
その後悪意がある人が、傍受したid_tokenをWebサーバにリダイレクトした場合、Webサーバ側にはnonceのcookieが存在しないため、nonceが一致せずにエラーになります(認証用のcookieが作成されない)。

response_type=codeの場合は、nonceチェックがありません。
それであれば、Webサーバはcodeを2重に受け入れてしまうのではないか、という疑問がわきますが、実際は後続のcode_challengeがあるので、仮にWebサーバがcodeを2重で受け入れても次のcode_challengeでエラーになる仕組みです。

code_challenge –認可コード(code)とトークン(token)要求の整合性チェック

【code -> token要求時にチェック】
codeを要求した人とtokenを要求した人が同じであるかをチェックする仕組みです。
アクセストークンを取得する際の流れとして、まず認可コード(code)を取得し、取得したcodeを受け渡してトークン(token)を要求します。
なので悪意のある人がcodeを傍受した場合、悪意のある人は傍受したcodeを使ってtokenを要求し、取得することができてしまいます。

認可コードはhttpsで送信され、かつ1回限りの使い捨てのため、誰かに奪われる心配は少ないですが、一応対策はあります。
それがcode_challengeです。

クライアントは認可コード(code)要求時にcode_challengeという、ランダムな文字列(後ででてくるcode_verifier)のハッシュをBASE64UInt変換した文字列と、code_challenge_methodという暗号形式(ASP.NET COREではS256というハッシュ暗号化方式)を一緒にOIDCサーバに送信します。
OIDCサーバはそれをセッション等に保存しておき、クライアントに認可コード(code)を返却します。
クライアントはトークンを要求する際に認可コード(code)と一緒にcode_verifierをOIDCサーバに送信します。
OIDCサーバはcode_verifierをcode_challenge_method(ここではS256)でハッシュ化およびBase64UInt変換して、その結果が該当の認可コード受信時に保存しておいたcode_challengeと一致していれば、認可コードの要求者とトークンの要求者が同じ人だと判断します。

client_idチェック

【code -> token or id_token要求時にチェック】
WebサーバからOIDCサーバのcodeやtokenやid_tokenの要求時にclient_idを渡します。
client_idがOIDCサーバに登録されていない場合、エラーになります。

署名チェック

【token or id_token返却時にクライアントでチェック】
トークンの中身が改ざんされていないかのチェックです。
OIDCサーバから返却されたid_tokenやaccess_tokenには署名がついてます。
署名はRSAなどの秘密鍵(secret key cryptosystem)やHS256などの対称鍵(symmetric key)などでid_tokenの内容を暗号化したものになります。
id_tokenやaccess_tokenが暗号化されているわけではなく、id_tokenやaccess_tokenの暗号化したものを署名としてid_tokenに付与しています。

Webサーバは署名を複合化し、id_tokenやaccess_tokenの内容と一致することを確認して改ざんされていないことを確認します。
複合化の際に、秘密鍵の場合は対になる公開鍵をOIDCサーバのjwks_uriから取得して複合化します。
対称鍵の場合は、事前に(システム的ではなく運用で)OIDCサーバ側と対称鍵を決めておき、WebサーバのファイルやDBなどに格納しておきます。
業務システムの場合は、秘密鍵でも対称鍵でもどちらでも問題ありませんが、Googleなどの一般向けのOIDCサーバの場合、事前に対称鍵を決めておくというのは手間がかかるので、秘密鍵にしている場合が多いように感じます。

Audience(クライアント)とIssuer(OIDCサーバ)のチェック

【token or id_token返却時にクライアントでチェック】
id_tokenやaccess_token自体は(規格にもよりますが)暗号化されていないBase64ベースの文字列になります。
なので簡単に平文にすることができます(id_token自体を暗号化する規格もあります)。
id_tokenやaccess_tokenにはtokenを要求した人(ここではWebサーバやBlazor WebAssembly)の情報が設定されているaud、
tokenを発行した人(ここではOIDCサーバ)の情報が設定されているissuer
があります。
audにはclient_id、issuerには該当のOIDCサーバを表す名前が設定されていることをチェックし、クライアントが要求したtokenであることを確認します。

なお、issuerは、Blazor WebAssemblyの場合は、OIDCサーバのメタ情報(.well-known/openid-configuration)で返却されるissuerとtokenに含まれるissuerを比較します。
AddOidcAuthenticationでAuthorityを指定できますが、その値は使用せずメタ情報を使用します。
※余談ですが、OIDCサーバのメタ情報から返却する「issuer」の項目を「issure」としていたことに気付かず、Blazor WebAssembly側でエラーになっており、丸2日間を無駄にしてしまいました。

有効期限チェック

【token or id_token利用時にクライアントでチェック】
id_tokenやaccess_tokenはクライアント(WebサーバやBlazor WebAssembly)側で認証情報として保存されます。
※Webサーバではデフォルトではcookieに保存され、Blazor WebAssemblyでは(たぶん)ブラウザのメモリ上に保存されます。

通常、トークンが漏洩するリスクは少ない(通信はhttpsで、Cookieのデータは暗号化されていて、かつブラウザを閉じるとcookieは削除される)ですが、漏洩した場合の対策としてtokenの有効期限のチェックがあります。
id_tokenやaccess_tokenにはExpiresがあり、これに有効期限(年月日時分秒)が設定されているので有効かどうかをリクエスト毎にチェックします。
有効期限は比較的短い期間にします(感覚的に1時間くらい?)。
トークンが漏洩したとしても、使おうと思った時にはトークンの有効期限が切れていて使えない、ということになります。