OAuth 2.0 と OpenID Connect を実装しながら学ぶ
OAuth 2.0 の認可フローは、RFC には 4つ定義してある。OAuth は認可のためのプロトコルなので、この5つでアクセストークンの取得の仕方が変わる。
そして、どれが一番良い方法なの?という話だが、最近は状況に応じて次の2つが使われるらしい。
- ブラウザやモバイルなどの自身の認証情報を安全に保持できない公開クライアントの場合
- Authorization Code Flow (with PKCE)
- サーバー間通信などの自身の認証情報を安全に保持できるクライアントの場合
- Client Credentials Flow
今回は Authorization Code Flow (with PKCE) を簡略化して作っていくことにしたい。
ちなみに PKCE (ピクシーと読む) とは、悪意のあるクライアントに redirect_url を利用してトークンを横取りされないための拡張仕様である。
今回は Authorization Code Flow (with PKCE) を簡略化して作っていくことにしたい。
クライアントはブラウザ上のアプリケーションを想定していて、リソースオーナー (自分) がクライアントから認可を要求されて、許可を出した後の流れ (Github とかの OAuth の画面で OK を出した後の流れ) の sequence 図は次のようになる。
Authorization Code Flow (with PKCE) と Client Credential Flow のベースを作った
auth server の token がただの uuid なので、これから JWT にして resource server との連携を実装する。そのあとに OpenID Connect を実装することにしたい。
JWT の話は、次の2つの記事がとてもわかりやすかった。
JWT の仕様としては JSON データを URL セーフにどう token 化するかしか書いてない。OAuth のアクセストークンを JWT で扱うときの仕様として、RFC 9068: JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens が重要になる。
アクセストークに紐づく情報をどう管理するかの方法としては、auth server の DB に保存して resource server が auth server へ API 経由で問い合わせる識別子型と、アクセストークンの中に情報を埋め込んでしまう (要は JSON を JWT で token 化するということ) 内包型の2つのパターンがある。
内包型の方が resource server から auth server へのリクエストがなくなって嬉しいが、もちろん暗号化や署名をしないと簡単に偽造されてしまうという問題がある。そこで 上記の RFC 9068 が重要になる。
RFC 9068 では、データの署名には JWS をデータの暗号化には JWE を使うことを規定している。どちらも IETF で策定されている仕様である。なお JWS は事実上必須になっている感じがある。
JWE の詳細はこちらも参考になる
JWS と JWE は、どちらも JSON 形式と Compat 形式があり、JWT では compat 形式を利用することが仕様で決まっている。JWT が ピリオドで連結されている形式をよく見るが、これは compat 形式由来である。
また署名や暗号化のアルゴリズムには、非対称鍵認証方式が推奨されている。この場合、auth server はリソースサーバーに公開鍵を配る必要があるが、これは JWKS と呼ばれる仕様を満たすエンドポイントを auth server が公開することで実現されることがある。
↑の流れを一通り実装してみた
最新の rsa crate で rand@8 系までしか対応してないことに気づかず少しハマった
次はとうとう OpenID Connect の実装かな〜と思ったけど、Token Introspection エンドポイントも実装した方が良さそうだよと chatgpt に教えてもらったのでみている。
要は token の発行と token の利用のタイミングが違うので、発行から利用までの間に token が失効したり、scope などが変化してないかを auth server に問い合わせることを可能にするエンドポイントのことみたい。
OAuth の実装していて well known url の存在を知ったけど、結構色々あるんだな
ID Token を返す実装までやった。 User Info エンドポイントとか /.well-known/openid-configuration とかの実装はしてない。
今気づいたけど、エラーが起きた時にどういうエラーを返すべきなのかもちゃんと仕様で定義されているのでそれに従うべきみたいだ
大体実装したので、終わり。OpenID Connect が OAuth の一部というか、OAuth のフローを利用していて、混同しやすいのがよく理解できた