はじめに
少し過激なタイトルを書いてしましましたが、認証と認可の基盤を作った自己の経験に基づきあくまで一つの考えとしてお話しさせていただきたいと思います。
また、認証=認可という密結合の考えを支持しているわけではありませんが、認証と認可を疎として作った場合の危険性とその難しさについて周知できればと思います。
なるべく非エンジニアにもわかるように書いてるので是非挑戦してみてください。
言葉の定義について
一般的には認証はシステムの利用者が誰であるのかを示すことが多く、認可はその中で何をして良いかというのを示すケースが多いので一旦その程で話を進めさせていただきます。
後ほどでNIST(アメリカ国立標準技術研究所)の例をもとに詳しい認証と認可の定義について紹介いたします。
運転免許証をどう使うか
早速例え話ですが、
もし、あなたが警察官ならこのケースで運転は認めますか?
顔の似ている似てないの議論はあるかもしれませんが、大体問題なく運転を認めると思います。
運転免許証があるので運転していいし、運転免許証の顔写真と運転者の顔も一致している。
ではこのケースはいかがでしょうか?
多分、大体の人は運転認めないと思います。
運転をしていい免許証は持っていた、しかし、免許証の顔と運転者の顔が一致していません。
現実世界の免許証
現実世界では免許証で運転という認可だけではなく、本人確認を行うなど認証の機能も兼ね備えており認証=認可に近いものになっていると思われます。
システムに戻して考えるとどういうことになるか?
昨今のシステムでは認証と認可を疎結合に作る考え方が流行っておりますよね。OAuth2.0なんかはその際たる例かと。まず、順当に認証と認可を行ってユーザー情報を取得するシステムを考えてみましょう。
この図を見て大体理解できる方は次の詳細説明は読み飛ばしてください
認証(ログイン)
OAuth2.0 において認証サーバーに対してログインを行うとトークンがレスポンスされます。この時のトークンはjwtトークンやOpaqueトークンなど様々ありますが、最近主流のjwtトークンを想定します。
jwtトークンの中にはログインしたユーザーやトークンの発行者などの情報が書かれており、閲覧自体はトークンさえ手に入れて仕舞えば実は誰でもできます。ではどうやってセキュリティを担保するのでしょうか?
認可
認証時に手に入れたトークンをもとにセキュリティを担保する処理がこの認可で行われます。テキトーにトークンが偽造できてしまえば個人情報が抜かれ放題ですよね?
そこで登場するのが 署名の検証 です。これが認可のフローに含まれ、そのユーザーの持ってるトークンが本当に認証サーバーに発行されたものかを確かめます。これによってトークンの偽造を防ぎます。
認可が完了したらDBからユーザーの期待する操作や情報をアプリケーションサーバーが返します。
普段これら一連の処理がデバイス側で行われるのでもし読者が非エンジニアだとしたら知る由もないですよね。(そんな読者がいるのかわからんけど)
問題
実は今の説明だけでは認可のフェーズにセキュリティ的に穴が存在します。どこだと思います?
そこで登場するのが 署名の検証 です。これが認可のフローに含まれ、そのユーザーの持ってるトークンが本当に認証サーバーに発行されたものかを確かめます。これによってトークンの偽造を防ぎます。
トークン内部に必要なもう一つの情報
正解はこの部分にあります。
トークンが本当に認証サーバーに発行されたものか
確かにトークンが認証サーバーから発行されたものかを確かめる必要はありますが、「誰に対して発行されたものか?」というのが重要です。
システムには具体的にどのような操作を行うかをデバイスからサーバーに指示するリクエストボディやクエリパラメータなどというものがあります。トークンとは別の領域に「誰であるか」というid情報を持たせた場合図のようにトークンの所有者以外の情報が閲覧できてしまいます。
そんなシステム誰が作るんだ?
少々システムの込み入った話なので疑問を持たなかった方は読み飛ばしてください。
「そんなアホな話あるか?」と思うのも真っ当な感想です。ただ、十分にこういうシステムが作られる可能性はあります。
例えば、ある日「同業の会社を買収したのでその公表日にウチのシステムとログインを統一してほしい」と急遽言われたとします。
認証の統一自体は簡単です、Auth0とかcognitoとか導入してフロントから呼び出せばいいので。
しかし認可も考慮に入れるとなると一筋縄ではいきません。
なぜなら別の会社となると自社とは異なるidで管理されています。
どうしよう、DBのidを全て書き換えて自社に寄せる、この案は誰しもやりたくないですよね。
他社のidを残す方法を取ると思いますが、この時jwtトークンや既存の認証サーバーに関して明るい知識がない場合、他社のユーザーidを中央管理するサーバーを作ってそれをリクエストボディに含めてやれとなる可能性も大いにあります。その話の実体験があるかないかは黙っておきます。
また、せっかくjwtのペイロードの中に他社のユーザーidを含めたとしても、フロントの構築者やアプリケーションサーバーの構築者がその意図を理解するかは別の話です。
シーケンス図を用いた詳しい話が気になる方はこちらの記事をご確認ください
認可の際には誰であるかの情報がある程度必要
話を戻します。タイトルにもあるとおり認可の際にも誰であるかの情報が必要です。認証の定義を「システムの利用者が誰であるかを明確にすること」とするとそれは認証と認可は目的が異なるがその処理の中で「ユーザーを特定できる」点で同一という特性を持ちます。
冒頭に免許証の例を出しましたが、そこでも所有者が誰であるのかというのがわかる状態になっていたと思います。
つまり現実的にもシステム的にも実際問題「誰」という点で認証と認可は繋がってしまうという特性があります。
ではなぜIDトークンが存在するか
また少し専門的な話を。ここまでトークンという一括りで話を進めてきましたが、前述の例はアクセストークンのことを示していました。しかし、OIDCというフローではIDトークンという「誰であるのかを証明するトークンが存在します。OAuth2.0のアクセストークンにidを含めるなら不要ではないかという考えもあるかも思いますが、目的の分離を考えるとその意見は違うと思います。
OAuth2.0で発行されるアクセストークンはあくまで認可のためであり、このトークンで他のシステムにログインや認証を行うのはあまり良くないと思います。あくまで認可のための必要最低限の情報を持たせるためです。一方、IDトークンにはユーザーidはもちろんメールアドレスなどの個人情報も含みます。なので認証のため用います。
アクセストークンとIDトークンの使い分け
この二つを使い分けが発生するシーンとしてはシステムの認証の統合などがあると思います。
先ほどの買収の話で出たようにシステムを統合する時AシステムのidとBシステムのidの紐付けを行う必要がありますよね。
Aというシステムの認証とBというシステムの認証ができるようにするために、Aというシステムから認証しIDトークンを取得します。その後Aシステムからログインした状態のBシステムに遷移しIDトークンを渡すことでメールアドレスなどを通じてユーザーidの突合ができます。このフローを安全に作るためには少々手間がかかりますが、署名の検証をすればなりすましも防止できます。
紐付けを行った後はAシステムの認証サーバーにBシステムのidを持たせてjwtに持たせてもよし、BシステムにAシステムのidを持たせてもよし。そのあとは認可のルールさえ作れたメールアドレスなどの個人情報をいたずらにBシステムに渡さなくても限定的なidを含むアクセストークンだけでシステムの認可ができます。
免許証が認証も認可もできるという話について
余談ですが、これに話を戻すとすると運転免許証は認可のために個人情報を持たせまくった結果、認証もできるようになったと考えています。免許証より先にマイナンバーカードとかが普及して一般化していれば免許証ももしかしたら個人識別番号と顔写真だけになっていたかもしれません。
認証と認可に対する考え方
ざらっと認証と認可の話を話してしまっていますが可能な限り客観的な視点も含めて考えてみます。
NIST(アメリカ国立標準技術研究所)のサイトでは先ほどの例とはもう少し詳細に定義されています。
認証
NISTの原文は英文のまま引用として記載しておりますが自分の解釈は以下の通りです。
認証とは操作の元が何(ユーザー、デバイスなど)かを確かめる処理のことで、その後のシステムのリソースを扱うための権限処理の前提条件となりうるもの。
The process of verifying the identity of a user, process, or device, often as a prerequisite to allowing access to resources in an information system.
基本的には誰が通信先にいるのかシステムが確かめるために使われますが、ユーザーではなくデバイスの存在を確認したいケースもあるのでそれも含まれます。
認可
認可も同様に、自分の解釈は以下の通りです。
認可とはシステムリソースにアクセスするための権限を与えること
The right or a permission that is granted to a system entity to access a system resource.
原文で出てくる system entity
というワードの解釈が日本語では難しいですが、デバイスやユーザーに対してどの情報を見せていいのかを扱うものと定義して良いものと思われます。
また、ここでは認可という言葉に対して「誰であるか」という言葉は用いられていないとは言えます。
言葉の定義からの考察
認証の説明文の中に prerequisite to allowing access to resources in an information system
と「情報リソースにアクセスするための前提条件」とあることから認証は多くのケースにて認可のために行われるとして考えれると思います。このことから、私は認証と認可というのは基本的には切っても切れない関係性と認識しています。
結論
システムを構築する際は認証と認可は可能な限り切り離して考えるべきです。ただ、いくら認証と認可を切り離そうとそのフローを辿ると必ずつながりは存在します。
また、外部IDなどを使えば認証と認可をより疎にすることは可能だとは思いますがシステム管理や構築、テストなどのあらゆる面で構築しそれを開発するエンジニアに理解させるのは少々難しいです。
何度も言いますが認証と認可は一緒にすべきではなく、筆者の感想としては認証と認可を疎にするのは難しいというのが感想です。
また、認証と認可を疎にするためにリクエストBodyにidを含めるという愚行が発生するリスクも考えると無理にそこを疎にする必要はないと思っています。
適切なセキュリティやプロダクトのフェーズにおいてそこの関係性を保っていければ良いのではないでしょうか?