このビデオは、2021 年 10 月 6 日に開催された 「挫折しない OAuth / OpenID Connect 入門」の理解を深める会 のプレゼンテーション録画です。
2021 年 9 月 18 日発売の「Software Design 2021 年 10 月号」では、OAuth/OIDC が特集され、「挫折しない OAuth/OpenID Connect 入門・API を守る認証・認可フローのしくみ」と題し、Authlete 代表の川崎貴彦が寄稿しました。
本プレゼンテーションでは記事のポイントや、理解を深めるために重要なポイントについて、著者の川崎がお話しします。
記事の第1章、第2章、第3章は、こういう目次になっています。 ここからピックアップして、 こんなことを話してます、というところを、 紹介したいと思います。
Authlete 社の代表取締役社長をやっています。 基本はエンジニアです。 ふだんは OAuth や OpenID Connect の規格策定作業に参加し、 それを製品の実装に落とし込む仕事をしています。
最近の仕様策定作業にもよく参加していて、 ここに挙げた技術仕様に名前が載っています。
あと、技術ブログも英語と日本語で書いています。 英語で書いた Financial-grade API の記事については、 OpenID Foundation の公式ウェブサイトに転載されたり、 ポルトガル語に翻訳されて、ブラジルのオープンバンキングで、 半ば公の資料のような扱いになっています。 日本語では Qiita というブログサイトに記事を公開しています。
2015 年 9 月に創業した会社です。 OAuth と OpenID Connect のサーバーの実装を提供しています。
とくに、Financial-grade API の分野では、 世界でもトップに入る企業です。
国内外を問わずお客さまがいらっしゃいます。 最近のお客さまの例では、Nubank というブラジルの銀行、 世界最大のデジタル専業銀行です。 BTG Pactual、ラテンアメリカ最大の投資銀行です。
国内では、みんなの銀行さん、セブン銀行さん、楽天銀行さん。 LINE Bank さんにもお使いいただく予定です。 その他、名前は書いていないのですが、 地方銀行さんにも間接的に使っていただいています。
では、さっそく始めます。
第1章では、OAuth と OpenID Connect の話を、記事では書いています。 3 ページくらいの記事を要約すると、このようなことを書いています。
OAuth 2.0 は、アクセストークンを発行するための手順を定めた技術仕様です。 アクセストークンは Web API を呼び出すときに使います。
OpenID Connect は、ID トークンを発行する手順を定めた技術仕様です。 超要約するとこんな感じです。 ID トークンは、ユーザーを認証した結果や、 ユーザー属性情報を利用したいときに使います。 シングルサインオンを実現するときに使ったりします。
アクセストークンを発行するときも、ID トークンを発行するときも、 ユーザーとサーバーがやり取りをして、 その後でトークンを発行する、という処理が、 非常に似通っています。
このひとつの処理の中で、アクセストークンも ID トークンも発行可能です。 OpenID Connect が出てくると OAuth 2.0 も出てくる、という話になります。
第1章では、こんなことをお話ししました。
第2章、知っておきたい仕様と規格の話です。
紙面の都合上、すべてを説明できません。 よって、いちばん使われる認可コードフローにだけ、 記事では説明しました。
認可コードフローの説明は世の中のいろんなところにありますが、 もう一度あらためて要点を述べます。
登場人物は、クライアントアプリケーション、 Webブラウザー、 認可サーバー、リソースオーナーです。
クライアントは最初に、Web ブラウザーを経由して、 認可リクエストを、認可サーバーの認可エンドポイントに送ります。
それを受けた認可エンドポイントは、 ユーザーに対し、ユーザー認証や同意確認のためのページを送ります。
ユーザーはその内容を確認し、返事をします。 許可を出したり、ユーザー認証に必要な情報を提供します。
その結果、問題が無ければ、 認可サーバーは認可コードを生成し、それを返します。
返す先は Web ブラウザーですが、 最終的にはクライアントに認可コードを渡します。 しくみとしてリダイレクトという特殊なこと、 HTTP ステータスコードの 300 番台を使って、 Web ブラウザーに、他のところにリクエストを飛ばしてもらいます。 リダイレクションエンドポイントに、認可コードが渡っていきます。
それを何らかの方法でクライアントに渡します。 これは実装依存です。 実装によっては、クライアントとリダイレクションエンドポイントが、 同一の場合もあります。
認可コードを受け取ったクライアントアプリケーションは、 認可サーバーのトークンエンドポイントにリクエストを投げます。
トークンエンドポイントは、認可コードがちゃんとしているか調べた後、 アクセストークンと ID トークンを生成して、 レスポンスとしてクライアントに返します。
認可コードは一時的なものなんですけど、 クライアントはアクセストークンと ID トークンがほしいわけです。 最終的に、このトークンレスポンスを受け取ります。
トークンレスポンスは、記事中にも書いているのですが、こういう見た目です。 中身は JSON です。 ここに黄色く、access_token と、 id_token があって、 そこに書かれている文字列が、 アクセストークンと ID トークンです。
ID トークンのフォーマットは JWT という形式で、仕様で決まっています。
よく誤解があるので一応言うと、 ID トークンは JWT(ジョット)の一種です。 一方アクセストークンは、その実装が JWT かもしれないし、 そうじゃないかもしれない、という関係の図です。
一応 JWT の説明の資料も用意しました。 あとで時間があれば、振り返って説明します。 今はちょっと飛ばしておいて、 次の図は、記事の中で紹介したものです。
これは何を示しているかというと、 ID トークンは、JWT の一種で、 JWT は JWS か JWE です、という関係を技術的に書くとこういう図になります。 JWT の説明については、後で時間があればやっていきます。
第3章は、ID トークンなりアクセストークンを取得して、 それをどう利活用するかという話を書いています。
最初に、アクセストークンの使い方です。 アクセストークンを使って API にアクセスしにいきます。
API アクセスにアクセストークンを含める方法が、 RFC 6750 という仕様に 3 つ定義されています。
3 つあるんですけど、ほぼほぼ 1 番目、 いろんな理由で、1 番目を使うのがメインです。 アクセストークンを HTTP ヘッダーの Authorization に入れて渡します。
具体的にはこんなふうになっています。 Authorization ヘッダーに Bearer と書いて、 アクセストークンの文字列を書きます。
curl コマンドで API へアクセスする場合ですが、 アクセストークンを含むアクセスの例を書いています。 -H オプションで、 任意の HTTP ヘッダーをリクエストに含められます。 ここで “Authorization: Bearer アクセストークンの値” を書いて、 あとはアクセスしたい URL です。 これで、アクセストークン付きの API アクセスが実現できます。
次に、ID トークンの話です。 記事の中で取り上げた例ですが、 ID トークンの中に入っている情報を抜き出すことは比較的かんたんです。 暗号化されていなければ。
ただ厄介なのは、その ID トークンについている署名が正しいものか、 署名を検証する手順です。 記事ではそこについても紹介しています。
どうやって ID トークンについている署名を検証するか。 署名検証のためには、検証用の鍵をゲットしないといけません。 その鍵を取得する方法はこんな手順です、という話を記事で紹介しています。
まずクライアントが ID トークンを取得したとします。 そのペイロードの中身を見ると、いろんなデータが入っています。
とくに重要なのは iss というクレームのデータです。 ここに IDトークンを発行した人の発行者識別子が入っています。 Issuer identifier と呼ばれるものです。iss とは issuer の略です。
iss の値がわかると、 その ID トークンを発行したサーバーのいろんな設定情報を、 どこから取得するか、場所がわかります。
どうやってわかるかというと、 iss の値の後ろに /.well-known/openid-configuration というパスをつけます。 その場所に、設定情報を返してくれる、 ディスカバリーエンドポイントがあることがわかります。
場所がわかるので、クライアントはここにアクセスし、 その返事として、ディスカバリー文書を取得します。 ディスカバリー文書の中には、いろいろなサーバーの設定情報が書いてあります。
その中に jwks_uri というのがあります。 サーバーはいくつかの理由で、公開鍵をある場所で公開しています。 その鍵群がどこで公開されているか、という場所を示しています。 この値を見ると、この例だと jwks というパスで公開されているとわかります。
JWK セット文書を公開している場所がわかるので、 そこにアクセスしに行きます。 その結果、鍵が渡ってきます。
これらは JWK セット文書という形式です。 keys というキーがトップレベルにあります。 その値は配列です。ここに鍵がリストされています。 この中の鍵のどれかが、ID トークンの署名の検証に使える鍵になっています。 探し出す方法はいろいろあるのですが、この中から一つ鍵を特定して、 署名の検証に使います。
いま見ていただいているプログラムは、 記事に載せたものを一部省略したコードです。
これは ID トークンの署名検証コードの例です。 例なので、ほぼほぼ決めうちで書いています。
メインは verify というメソッドです。 JWS と、JWK を引数として、 検証するメソッドになっています。
ソースコード内では ID トークンの値も public key(公開鍵)の値もハードコーディングしています。 JWS 形式の ID トークンと、JWK 形式の公開鍵を、メインのメソッドの verify に渡しています。
この verify の中では、 まず文字列 を JWS としてパースして JWSObject にします。 ID トークンが JWS の前提なので、このようにパースしています。
暗号化されている場合には JWE なので違う処理になりますが、 ここは決め打ちで JWS でパースし、 その後 JWK を、これは公開鍵なので、汎用的な JWK としてパースします。
この例では、そのJWK の公開鍵が楕円曲線系の暗号の公開鍵だとわかっています。 ここでもハードコーディングして、EC のキーとみなして、 変換して、さらに公開鍵の部分だけ取り出します。 ECKey で受けます。 これが楕円曲線アルゴリズムではない場合は、他の鍵に変換します。 ここでは決め打ちで、こうやっています。
公開鍵ができたので、楕円曲線暗号の署名を検証する verifier を生成します。 JWS の verifier を使って検証します。
このあたりが、ID トークンの署名の検証のコード例です。 これで ID トークンの署名の検証ができます。
これで何がうれしいかといえば、検証が通れば、 ID トークンは途中で改ざんされていないとか、 ちゃんと意図した発行者によって発行されたものと確認できることになります。
それで確認はできるんですけど、ID トークンの中身自体は別です。
検証が終われば、ID トークンの中身は、 ちゃんとしたデータとして利用できます。 ただ、 ID トークンの検証は、署名の検証だけではなく、 その後にもいろいろやらなければいけないことがあります。 記事中では言及していなくて、 OpenID Connect の 仕様書を見てください、と省略しています。
参考までに、OpenID Connect Core 1.0 仕様の 3.1.3.7 ID Token Validation という項目に、 たとえばこういう ID トークンに関してはこういう検証をしないといけない、 みたいなことが書かれています。 本気でやるには、ここらへんをちゃんと見て検証していくことになります。
ID トークンについてはこのようにやります。
第3章では、アクセストークンの情報の取得だとか、 アクセストークンの失効の話をしています。
その話をする前提条件として、 アクセストークンの実装の分類、 大まかな分類を知っておかないと、(実装によって)やり方が違ってくるので、 ここで説明しています。
これは以前の勉強会で使ったスライドです。RFC 6749 から抜粋した文言です。
アクセストークンには identifier(識別子型)もしくは self-contained(内包型)に大別できる、という話が仕様書に書いてあります。 これらを組み合わせるハイブリッド型もあります。
これらの型がどう違うか。 アクセストークンの実装が識別子型の場合は、 アクセストークンにひもづく情報そのものは、 認可サーバー側のデータベースに書いてあります。
アクセストークンテーブルを参照できる識別子を、アクセストークンとして渡すのが、 識別子型のアクセストークンです。
一方、内包型アクセストークンは、 アクセストークン自体にいろんな情報を埋め込むかたちです。
アクセストークンがどんな権限を持っているか、とか、 どのクライアントに与えられたアクセストークンなのか、とかです。
ハイブリッド型は、内包型のアクセストークンだけど、 全部の情報を必ずしもこのアクセストークンに持っているわけではありません。
一部は認可サーバーに保持したい場合に、 アクセストークンの中の、たとえば jti クレームを使って識別子を埋め込んでおいて、 そこから参照できるようにします。
ハイブリッド型の利点は、 有効期限とかのかんたんなチェックは、 認可サーバーに問い合わせずにできるところです。 それ以上のことがあったらこっちに行きます。
あと、アクセストークンは暗号化されていないと情報が丸見えになってしまいます。 どうしても隠しておきたい情報を認可サーバーに置いておきたい場合、ハイブリッド型を活用したりします。
このように、大きな 2 つの分類があります。
これによって、アクセストークンの情報の取得方法が変わってきます。
識別子型アクセストークンの情報は認可サーバーにしかありません。 よって、認可サーバーが用意している情報取得用の API に問い合わせることになります。 その情報取得用の API をイントロスペクション API と呼びます。
リソースサーバー(API の実装)は、アクセストークンに関する情報がほしいので、 その情報を教えてください、 と認可サーバーのイントロスペクション API に問い合わせます。
イントロスペクション API の実装としては、 問い合わせの中からアクセストークンを取り出し、 自分が持っているデータベースの中を検索して、 そのアクセストークンに関する情報を取得して返します。
このイントロスペクション API は標準仕様が存在します。 RFC 7662 です。 この API に対して、POST メソッドで、 token パラメーターにアクセストークンまたはリフレッシュトークンをつけて問い合わせると、トークンの情報が返ってきます。
オプショナルですけど、token_type_hint パラメーターというのもあります。 これは token に指定したトークンが、 アクセストークンなのかリフレッシュトークンなのかを示す、 リクエストパラメーターです。
ただし、これはただのヒントに過ぎません。 無くても構わないし、 仮に、token で指定したものがアクセストークンなのに、 token_type_hint で refresh_token を指定したとしても、 エラーになりません。
エラーにならないばかりか、 間違っててもちゃんと検索しなさい、と(仕様で)言われています。 処理はエラーにならないばかりか、検索は続行される、という仕様です。 これが標準仕様としてあります。
イントロスペクションレスポンスは、JSON で返ってきます。 active 以外は全部任意項目です。 active は、true か false の真偽値で、 アクセストークンが認可サーバーから見て有効かどうかを示している値です。
なにがアクティブかは認可サーバーの実装次第です。 少なくとも期限切れではないということは、たぶんこれでわかります。
ただ、そのアクセストークンが、 その API にアクセスするために必要なスコープを兼ね備えているかのチェックは、 認可サーバーではやりません。よって、 API 実装側でチェックしなくてはいけません。 たとえば、自分は API で、read_profile というスコープが無い場合にはアクセスを拒否する、という実装をしたいとします。 そのようなチェックはリソースサーバー側でやる、ということです。
このように、識別子型は、問い合わせないと情報を取得できません。
内包型アクセストークンの場合は、 アクセストークンの中に情報が入っているので、 いちいち問い合わせなくても情報が得られます。
ただ、記事にも書きましたけれども、 内包型アクセストークンは、放っておくとかんたんに偽造できてしまうので、 偽造をちゃんと検出できるように、ふつうは署名をつけます。
その署名の検証をちゃんとしないと 、 アクセストークンの中身が正しいかどうかわからない点は注意です。
失効に関してですが、これもアクセストークンの実装方法によって、 無効にする方法が変わります。
識別子型アクセストークンの失効は、 アクセストークンの情報自体が認可サーバーのデータベースに入っているので、 データベースレコードからそのレコードを削除してしまえば、 アクセストークンは無くなります。
一方、内包型アクセストークンの場合は、 Public Key Infrastructure の CRL や OCSP 相当の実装のしくみを運用しないと、 本来の期限が切れる前に失効させることはできません。
CRL は Certification Revocation List の略です。 期限切れした X.509 証明書のシリアル番号一覧を載せたリストです。 このリストがあると、証明書を受け取ったときに、 そのリストにもしその証明書が含まれていれば、 失効していることが確認できます。
問題は、失効した証明書のリストが肥大化してしまう可能性です。
あとは、失効リスト自体を取得するタイミングです。 リボケーションリスト取得のタイミング、同期の方法をどうするか。 たとえば、1 日に 1 回更新する、取得しにいくとなると、 最大 24 時間くらいは、 失効したはずなのに有効と認識され続ける証明書が出てきます。
これを許容できる場合もあれば、 セキュリティ上、24 時間もそんなことになっては困る、 無効にしたのに、まだ有効と認識されると困る、という場合もあります。
一方で、OCSP は Online Certificate Status Protocol です。 これは一種の API で、 証明書の失効状態をリアルタイムに問い合わせるしくみです。
問い合わせると、失効されてます、されていません、というのがわかります。 ただこれは通信が毎回発生するので、 パフォーマンスを気にする方は、いやだなあとなります。
このような、PKI の CRL や OCSP 相当のしくみを、 内包型アクセストークン、典型的には JWT 型の場合、 やらないと無効化できません。
仮に、CRL や OCSP 相当のしくみを構築するためには、 アクセストークンを一意に識別できる必要があります。 JWT 型のアクセストークンであれば、 jti クレームを使えば実現可能です。
その後、アクセストークンを失効させるたびに、 その一意識別子を「失効されたアクセストークンのリスト」に追加します。 当該アクセストークンの元々の有効期限が切れるまでは、 識別子をリスト内に保持しておく必要があります。
リソースサーバーは、受け取ったトークンの失効状態を確認します。 そのリストと照らし合わせるんですね。
CRL 相当のしくみを運用する場合は、 失効したアクセストークンのリストを取得して、 当該一意識別子が含まれていないかを確認します。
OCSP 相当の場合は、OCSP レスポンダー(OCSP 機能を提供する API)に相当する、 アクセストークンの失効状態を返す API に問い合わせます。
OCSP 相当のしくみを図にすると、こんな感じです。 認可サーバーが、アクセストークンの失効状態を返す API を用意して、 リソースサーバーはアクセストークンの中から識別子を取得して、 それを使ってアクセストークンの状態を問い合わせて、 API は失効状態を返します。
失効状態確認のために認可サーバーに問い合わせると、 識別子型アクセストークンにおいて、 イントロスペクション API に問い合わせるのと同じように、 ネットワーク通信が発生してします。 これをやると、内包型アクセストークン最大の利点が損なわれてしまいます。
図にすると、似ているのがわかります。 失効状態の問い合わせ API と、 情報を問い合わせるためのイントロスペクション API は、 やっていることは一緒です。
イントロスペクション API のレスポンスの中の、 active パラメーターに注目すれば、 イントロスペクション API こそ、 アクセストークンの失効状態を返す API そのものであるとわかります。
せっかく内包型アクセストークンにしたのに、これをやると、 なぜ内包型アクセストークンを選択したのか、 みたい話になってしまいます。
失効状態の問い合わせをしているようでは、 内包型アクセストークンをわざわざ積極的に選ぶ理由はありません。
内包型アクセストークンを選ぶ場合は、 アクセストークンの有効期限をできるだけ短くして、 失効をあきらめる、というシステム設計になります。
それを踏まえて、そうだと分かっていても、 内包型アクセストークンを利用しないといけない理由があるとすれば、 何らかの事情により、 リソースサーバーと認可サーバーが通信できないという場合です。
トークンの失効 API も標準化されています。 これは記事でも紹介しました。
OAuth とか OpenID Connect のすごい細かい話は、 記事では入門編しか書いていません。 でももっと、話さなくてはいけないことがいっぱいあります。
そこらへんについて、Authlete 社が過去にオンライン勉強会をして、 録画を全部公開しています。
「アクセストークン」、「認可リクエスト」、「クライアント認証」、 それぞれだけで 2 時間以上話すくらい、内容がてんこ盛りです。 ご興味のある方は、ビデオを参照していただければと思います。
あとは、せっかく勉強会に今日来てくださった方におまけの情報を提供します。 最近の動向の中から、ちょっとだけご紹介します。
Financial-grade API (FAPI) というものがあります。 これは 2017 年 2 月ぐらいからずっと議論してきている、 セキュリティレベルの高い API を実現するための規格です。
今年の 3 月、FAPI 1.0 の、 ファイナルバージョンが承認されました。 これで FAPI 1.0 が完成しました。
一方で、FAPI 2.0 の議論が始まっています。 FAPI 2.0 は、Baseline と Advanced という 2 つがメインの仕様となります。 FAPI 2.0 Baseline は 7 月に承認されています。 FAPI 2.0 Advanced は、これからの仕様です。
特徴的なのは、 Baseline プロファイルと一緒に承認された、 FAPI 2.0 Attacker Model という仕様があります。
プロトコルの攻撃者はどういう攻撃をするのか。どういうところに脆弱性があるのか。 攻撃者はこんな感じですよ、ということを想定する文書です。
想定する文書を書いて、 ではその攻撃を防ぐにはどういう仕様であるべきかを考えよう、 という手順をとっているのが FAPI 2.0 です。
この Baseline と Advanced が大きな仕様になるんですけど、 FAPI FAQ というページを見ると、 FAPI 2.0 を構成する技術コンポーネントが列挙されています。 これがすべてではありませんが、 このような感じです。
PKCE は昔からある仕様です。
DPoP。
PAR (Pushed Authorization Requests)、これは最近 RFC になりました。
Rich Authorization Requests も、まだドラフト段階ですが、これも入ります。
iss レスポンスパラメーター も、ドラフト段階ですが入ってきます。 この iss 認可レスポンスパラメーターですが、実は記事中で、 認可レスポンスの例の中にこの iss パラメーターを含めてあります。 記事をお持ちの方は確認してみてください。 しれっと入れてあります。
あと、FAPI2 の位置づけについての議論を、 ワーキンググループでやっています。 FAPI2 のトラストフレームワークを、 このようにポジショニングしましょう、と言っています。
赤い枠が FAPI2 のコンポーネントです。
この後ろ、角が丸くなっている四角は、 各国のオープンバンキングとか、 各国固有に必要な技術要素です。 たとえば UK Open Banking とか、Brazil Open Banking とかです。 それぞれのエコシステムには、それぞれの API があったり、 それぞれのプロファイルがあります。 Open Banking Profile とかいう感じです。
でその中のセキュリティ周りは FAPI2 でカバーしていきましょう、 キーワードとしてはさっき言った Baseline セキュリティプロファイルや、 Grant Management という、新しい仕様が入ってきたり、 昔からある FAPI / CIBA、 あとは RAR (Rich Authorization Requests) とか、 SSE (Shared Signals and Events) という仕様があったり、 この DCR (Dynamic Client Registration) は昔からあるやつです。
FAPI2 は、最近はこんな感じです。
最後にもう一個、最近のアップデートが、 OpenID Connect for Identity Assurance です。
歴史的には、2019 年 11 月にこの仕様の ID1 (Implementer’s Draft 1)、 一番最初のものが出て、2020 年 5 月には 2 番目が出ました。
いま 3 番目の仕様が出ようとしています。 2021年 9 月 18 日、いまから 2、3 週間くらい前に、 その第 3 版のパブリックレビューが始まっています。
パブリックレビューは 45 日間続きます。 この間に大きな問題が発見されない限り、 スケジュール通りいくと 11 月の初めぐらいに、 Implementer’s Draft 3 が承認されます。
この仕様は Identity Assurance という、 OpenID Foundation の eKYC and Identity Assurance ワーキンググループで、 議論されている仕様です。
そのワーキングループで議論している仕様は、 もちろん Identity Assurance がメインなんですけれど、 他にも 2 つ仕様があります。
ひとつは、OpenID Connect Authority Claims Extension という仕様です。
Identity Assurance は natural person(自然人)の情報を表現する仕様ですが、 この Authority の仕様のほうは、legal entity(法人)の情報をどう表現するか、 というところをやっています。
この仕様に関して、日本の経済産業省 (METI) が注目しています。 実際にこの仕様を読んで、METI が、 仕様をこうしたほうがいいんじゃないかと、 ちゃんとフィードバックもしてくれています。
それに基づいて、eKYC & IDA ワーキンググループが PR (Pull Request) を書いて、 仕様を調整・変更したりする、というのがあります。
もう一個は、OpenID Connect Advanced Syntax for Claims (ASC) です。
ユーザーの属性情報を問い合わせるときに、 その属性情報の細かい問い合わせを、 ちょっと小さなコンピューター言語みたいな方法で行うための仕様です。 こういうのも今後出てきます。
ここらへんが最新のアップデートです。