目次
はじめに
僕が開発リーダーをやっている開発チームのメンバーには認証認可周り詳しい人がおらず、
SPAとAPIとリリース間際の脆弱性診断で認証認可周りの突っ込みを受けて
リリース間際に自分で一から調べて自分で対応するという悲惨な目にあいました、、、、
そんな悲惨な経験から
SEやプログラマーの方が最低限理解しておくべきだと感じた、
認可コードフローの全体概要をまとめました。
2022/3/26追記
KeycloakでSpring Bootで作成したRestAPIを保護、
SPAにKeycloakのSSO機能でログイン機能を追加、
OIDCやOAuth2.0の違い解説などなどをまとめたまとめ記事を作りました。
興味のある方は是非みてください!!

SPAに認証機能を付けたシステムの構成
長くなるので、今回は実際に認可サーバの設定、フロントエンド、
リソースサーバの細かい設定や開発周りは省略しますが、
アプリの構成はこんな感じで、
ローカル環境でこれらを動かして動作確認した際の
実際のリクエストやレスポンスを例に交えながら説明していきます。
SPAでログインしてリソースサーバからリソースを取得するまでのフロー概要
絵にするとこんな感じ
上のフローで重要なポイントに絞って説明していきます。
認可リクエスト
keycloak-jsを利用して未ログイン状態でユーザがSPAにアクセスしたら、
認可リクエストパラメータを作成して認可エンドポイントにアクセスします。
ここではパラメータについての詳細な説明は省略しますが、
興味がある方はAuthlete川崎さんの>>OAuth 2.0 全フローの図解と動画
を参考にするのがオススメです。
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
認可コード取得
ログイン画面で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
アクセストークンリクエスト、アクセストークン返却
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はAuthorization ヘッダ付きでリソースサーバのエンドポイントをコールし、
リソースサーバはspring-boot-starter-oauth2-resource-serverを利用して
認可サーバのトークンイントロスペクションエンドポイントを利用して
アクセストークンを検証します。
ちなみに上記の②~⑦までのアクセストークン発行までの仕様を定めているものが
OAuth2.0と呼ばれるものであり、
それを発展させてIDトークン発行の仕様を定めているものがOIDCです。
まとめ
ここまで、アクセストークンが発行されるまでの流れや、
発行されたアクセストークンをリソースサーバで
トークンイントロスペクションで検証してリソースを返却する流れを見てきました。
SPAの開発に携わるエンジニアの方
(バックエンド、フロントエンド、インフラに関わらず)は
最低限この辺りはざっくり理解しておくことで、
認証認可周りのどこかで問題があったときに原因調査のあたりがつけやすくなります。(多分。。。)
さらに、
今回説明した認可コードフローの概要がつかめたら、
Financial-grade API(FAPI)といったさらに高セキュアな仕様も
そこまでつまることなく理解できるはずです。
(ちなみにkeycloakはFAPIや他にもCIBA等にも対応しており、
まだまだ使い倒したいと思っています。)