"Keycloak - Identity and Access Management for Modern Applications - Second Edition"を読んでいます。
邦題『実践Keycloak』の2nd Edition版ですね。
この本のChapter 2のワークを進める上で一瞬ハマりかけました。
エラーログを読んで冷静になったらすぐに解決できたのですが、理解のおさらいも兼ねて思考過程をメモしておきます。
Keycloakはもちろん、OAuth、OIDC、CORS等には入門したばかりなので、違うこと言ってる可能性がかなりあると思いますが、これも勉強のうちということで…。
利用したサンプルアプリケーションについて
本書で用意されていたサンプルアプリケーションについて前提整理のためにざっくり書いておきます。
概要
- Node.jsで実装されたSPA
- KeycloakをIdPとしたOIDC Authorization Code Flowを利用したユーザーログイン機能を実装
- ログイン処理後、Keycloakより返されたID TokenやAccess Tokenを画面に表示できる
- KeycloakにてClient登録済み
- 登録したURL等は以下
想定される挙動(一部Keycloakのライブラリが内部的に実行していると思われる)
- サンプルアプリケーションにてログインボタンを押す
- KeycloakへAuthorization Code Requestが送られる
- Keycloakのログイン画面が表示される
- Keycloakでユーザーがログインする
- サンプルアプリケーションの
Valid redirect URIs
として登録したhttp://localhost:8000/
にリダイレクト。パラメータにAuthorization codeを含む - サンプルアプリケーションがAuthorization code含めたリクエストをKeycloakのToken Endpointに送信する
- KeycloakからID TokenとAccess Tokenがサンプルアプリケーションに返される
- サンプルアプリケーションはID TokenとAccess Tokenを画面に表示する
CORS policyによるアクセス拒否
サンプルアプリケーションの仕様ではログイン後に取得したID TokenやAccess Tokenが画面に表示されるはずが何も表示されませんでした。
ChromeのDeveloper Toolを開いてConsoleのログを見るとエラー文が。
Access to XMLHttpRequest at 'http://localhost:8080/realms/myrealm/protocol/openid-connect/token' from origin 'http://localhost:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
CORS policyでサンプルアプリケーションのオリジン(http://localhost:8000
)からのリクエストが許可されてないとのこと。
Web originsは設定しているはずだが…
前述した想定される挙動の「5. サンプルアプリケーションがAuthorization code含めたリクエストをKeycloakのToken Endpointに送信する」にてエラーが発生していると思われます。
サンプルアプリケーションはフロントエンドのJavascriptによってリクエストを送信しており、リクエスト先であるKeycloakのCORS policyによってオリジンが許可されている場合のみリクエストが成功します。
そしてKeycloakではClientのWeb origins
にて設定された値をAccess-Control-Allow-Origin
に追加する模様。
事前の設定で追加しているはずなんだが…
おや…?
おやおや…?
Web originsの設定値にトレイリングスラッシュが入っていた
http://localhost:8000/
をWeb origins
に登録していたが、よくよくエラー文を見てみるとorigin 'http://localhost:8000' has been blocked by CORS policy
と書かれています。
そう。犯人は末尾の/
。
本書を読み返してみると、確かに文中ではWeb origins
の値のみ末尾の/
が記載されていませんでした。読み間違いやらかしてたー。
1つだけ弁明させてもらうと、本書に添付されていた設定画面のスクショでは/
がしっかり入っている値が表示されているのです。少し罠だと思いました。
そしてWeb origins
の値をhttp://localhost:8000
へと修正したら見事解決。
ちなみに末尾の/
は「トレイリングスラッシュ」というらしいです。
オリジンにトレイリングスラッシュは含まない
となるとオリジンの定義が気になるところなので調べてみました。
定義としては
Web content's origin is defined by the scheme (protocol), hostname (domain), and port of the URL used to access it. Two objects have the same origin only when the scheme, hostname, and port all match.
とのこと。これだけではトレイリングスラッシュを含むかどうか判断しにくいなと思いましたが、オリジンの例を見てみるとどれも末尾に/
がなく、オリジンというものはトレイリングスラッシュを含まないみたいですね。
These are not same origin because they use different hostnames:
よくよく考えたら HTTP requestは以下のような形式なので、オリジンにトレイリングスラッシュが入ったらおかしなことになりそうではありますね。
GET / HTTP/1.1 Host: example.com
Valid redirect URIsはトレイリングスラッシュが必要
ちなみに、Valid redirect URIs
の方は逆にトレイリングスラッシュ必須なの?というのが気になったので検証してみると、想定挙動の「2. Keycloakのログイン画面が表示される」にてエラーが発生したため必須ぽいです。
このときのKeycloakへのリクエストURLを見てみると以下のようになっていて、redirect_uri
の値には末尾に/
(エンコードされて%2F
になっている)が設定されていることがわかります。
http://localhost:8080/realms/myrealm/protocol/openid-connect/auth?client_id=myclient&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2F&state=以下略
コード実装上では以下のようになっており、redirect_uri
はKeycloakライブラリが設定している模様。
var kc = new Keycloak({ realm: 'myrealm', clientId: 'myclient' }); ~~略~~ <button onclick="window.kc.login()">Login</button>
ここでのredirect_uri
はおそらくアクセス元から生成していて、トレイリングスラッシュをつけるようになっているのではなかろうか。そしてKeycloakのClientに設定した Valid redirect URIs
に一致するものがあるかチェックしてる気がします。
(ライブラリのソースも少し読んでみたがすぐにはわからなそうで断念したので憶測です)
そもそもブラウザは基本、トレイリングスラッシュがないURLの入力には自動でトレイリングスラッシュをつけるらしく、Keycloakライブラリが設定するredirect_uri
にトレイリングスラッシュがついてるのもそういう流れなのかもしれないですねえ。
参考
- Keycloak - Identity and Access Management for Modern Applications - Second Edition
- RFC 6749 - The OAuth 2.0 Authorization Framework
- Final: OpenID Connect Core 1.0 incorporating errata set 1
- Origin - MDN Web Docs Glossary: Definitions of Web-related terms | MDN
- Is trailing slash automagically added on click of home page URL in browser? - Webmasters Stack Exchange
- urlの最後の「/(スラッシュ)」あり・なしの違いは?必要性について解説 | WEB集客ラボ byGMO(GMO TECH)