はじめに
OAuth2.0やOIDCといったプロトコルの仕組みのベースになっている
認可コードフローについてSPA(Single Page Application)にSSO機能を
keycloak(認証認可サーバ)で付与する例を使って図解しました。
僕が開発リーダーをやっている開発チームのメンバーには認証認可周り詳しい人がおらず、
SPAとAPIとリリース間際の脆弱性診断で認証認可周りの突っ込みを受けて
リリース間際に自分で一から調べて自分で対応するという悲惨な目にあいました、、、、
そんな悲惨な経験から最低限理解しておくと役に立ちそうなポイントに絞って、
認可コードフローの全体概要をまとめました。
SPAにSSO認証機能を付与したシステムの構成概要
今回は実際に認証認可サーバの設定、フロントエンド、
リソースサーバの細かい設定や開発周りは省略しますが、
アプリケーションのシステム構成はこんな感じで、
ローカル環境で動作確認した際の
具体的なリクエストやレスポンスを例に交えながら認可コードフローについて説明します。
SPAでSSOしてリソースサーバ(Rest API)連携するまでの認可コードフロー
認可コードフローを絵にするとこんな感じです。
上のフローで重要なポイントに絞って説明していきます。
認可リクエスト|SPA→認可サーバ
未ログイン状態でユーザがSPAにアクセスしたら、
SPAはkeycloak-js(認可サーバのアダプタ)を利用して
認可リクエストパラメータを作成して認可エンドポイントにアクセスします。
GET http://localhost:8180/auth/realms/レルム名/protocol/openid-connect/auth
?client_id=frontend
&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F
&state=1cda14d9-cd93-4744-89b6-808b799c8013
&response_mode=fragment
&response_type=code
&scope=openid
&nonce=62e3d51f-5f96-40a8-8e33-34fed33dc08a
&code_challenge=x7dfF7VqbW1oA0HvuLgZATVwkgfoo_3zeGukAPXKsgo
&code_challenge_method=S256
ここではパラメータについての詳細な説明は省略しますが、
興味がある方はAuthlete川崎さんの以下の記事がわかりやすいのでオススメです。
認可コード取得|認可サーバ→SPA
ログイン画面でIDとパスワードを入力してログインすると
認可決定エンドポイントにアクセスして、リダイレクトで認可コードが返却されます。
HTTP/1.1 302 Found
Location:http://localhost:3000/
#state=1cda14d9-cd93-4744-89b6-808b799c8013
&session_state=718f7bc5-da43-4fa7-b86f-6a936475203e
&code=1e5712f4-8d9d-4009-9089-32639892d871.718f7bc5-da43-4fa7-b86f-6a936475203e.a58f25ac-2660-414d-ae3f-725ec78465b1
アクセストークン取得|SPA→認可サーバ
keycloak-jsでアクセストークンを取得してリソースサーバ呼び出しのために、
例えばaxiosの共通ヘッダのAuthorizationに設定しておきます。
POST http://localhost:8180/auth/realms/resona/protocol/openid-connect/token
code: 1e5712f4-8d9d-4009-9089-32639892d871.718f7bc5-da43-4fa7-b86f-6a936475203e.a58f25ac-2660-414d-ae3f-725ec78465b1
grant_type: authorization_code
client_id: frontend
redirect_uri: http://localhost:3000/
code_verifier: E1c0gRF13FrdGf06WxhpB4qlzftePoTAHhf2ZJ7GxygcEKvu81FUAnhFqvSJTa6G3zBq2caFfX1XFWQnaOevdxAEaw6hL2t7
{"access_token":"eyJhbGciOiJSUzIH・・・"
,"expires_in":60
,"refresh_expires_in":140
,"refresh_token":"eyJhbGciOiJIUzI1・・・"
,"token_type":"Bearer"
,"id_token":"eyJhbGciOiJSUzI1・・・"
,"not-before-policy":0
,"session_state":"718f7bc5-da43-4fa7-b86f-6a936475203e"
,"scope":"openid profile"}
今回はアクセストークンをkeycloak-jsライブラリの変数で保持させていますが、
localStorageや変数で保持させているとXSS攻撃を受けると
アクセストークンにアクセス(抜き取られる)できてしまいます。
その対策としては、
そもそもXSSに対する根本対応を取るべきだとは思いますが、
ただ、、、
アクセストークンを盗まれても
それだけでは不正にリソースサーバにアクセスできないようにDPoPを導入するか、、
フロントエンドにアクセストークンを晒さないようにBFFを用意して
そこで認可サーバとやり取りをさせる仕組みにした方がよりセキュアです。
(が、、、対応工数と効果と求められるセキュリティ要件とを
天秤にかけて僕が携わっているプロダクトでは現状そこまでやっていないです)
リソースアクセス〜トークン検証|SPA→リソースサーバ→認証認可サーバ
SPAはAuthorization ヘッダ付きでリソースサーバのエンドポイントをコールし、
リソースサーバはspring-boot-starter-oauth2-resource-serverを利用して
認可サーバのトークンイントロスペクションエンドポイントを利用して
アクセストークンを検証します。
先ほどの図の②~⑦までの
アクセストークン発行までの仕様を定めているものが
OAuth2.0と呼ばれるものであり、
それを発展させてIDトークン発行の仕様を定めているものがOIDC(OpenID Connect)です。
まとめ
OAuth2.0でアクセストークンが発行されるまでの、
認可コードフローの流れや、
発行されたアクセストークンをリソースサーバで
トークンイントロスペクションで検証してリソースを返却する流れを見てきました。
SPAの開発に携わるエンジニアの方
(バックエンド、フロントエンド、インフラに関わらず)は
最低限この辺りはざっくり理解しておくことで、
認証認可周りのどこかでセキュリティ的に問題があった際、
原因調査のあたりがつけやすくなります。
さらに、
今回説明した認可コードフローの概要がつかめたら、
Financial-grade API(FAPI)といったさらに高セキュアな仕様も
ある程度すんなり理解できるはずです。
(ちなみにkeycloakはFAPIや他にもCIBA等にも対応しており、
まだまだ使い倒したいと思っています。)
おまけ
Spring Bootで作成したRestAPIを認証認可サーバで保護したり、
OIDCやOAuth2.0の違いを図解したり、
認証認可周りの諸々をまとめた記事を作りました。
是非ご覧ください。