「Keycloak と React で動かして学ぶ OpenID Connect」というタイトルで DevelopersIO 2023 大阪に登壇しました! #devio2023

2023.08.01

こんにちは。小売流通ソリューション部の中島です。

2023/7/19(木)にDevelopersIO2023大阪に登壇しました。

久しぶりのオフラインでの開催とのことでしたが、非常にたくさんのみなさまにご来場いただきました。 こういう場所で登壇するのは初めての経験だったので拙い部分もあったかと思いますが、足を運んでくださったみなさま誠にありがとうございました!

セッション内容

OpenID Connect について勉強してみたんだけどいまいちどういう風に動いているのかわかっていない・・・という方向けに、KeycloakとReactを使ったデモアプリを動かしながら、認可コードフローと照らし合わせて、ブラウザのDevツールを使ってリクエストを見ていくことで理解を深めていこう、という内容でお話しさせていただきました。

資料

デモの内容

実際の登壇ではデモ動画をお見せした箇所について、補足説明させていただきます。

P.7 まずデモアプリの動きを確認

まず、KeycloakとReactで作成したデモアプリの動きを確認します。

トップページから「Login」ボタンをクリックします。

Keycloakのログインの画面に遷移しました。

Keycloakの画面からあらかじめ作成しておいたユーザー名とパスワードを入力して「Sign in」をクリックします。

Reactのログイン後の画面に遷移しました。Keycloakから取得したユーザーの情報も画面上に表示されているのがわかります。

Keycloakを利用してReactアプリのログインを行うことができました。

シンプルですが、裏ではOpenID Connectのフローが動いているということをおさえておいていただければと思います。

P.23 Keycloak側の設定内容確認

Keycloak側の設定として管理画面にログインし、realm, client, userを作成します。

realmの作成

管理画面左側のメニューから 「Create Realm」をクリックします。

Create Realmの画面からRealm name(今回は「myrealm」)を入力して「Create」をクリックします。

これでrealmの作成は完了です。

clientの作成

次にclientを作成します。 管理画面左側のメニューからClientsをクリックします。

「Create client」をクリックします。

Client IDには「myclient」と入力します。

Capability Configは変更せずに「Next」をクリックします。

Login settingsには以下の項目を入力して「Save」をクリックします。

  • Valid redirect URIs: http://localhost:5173/authentication/callback
  • Valid post logout redirect URIs: http://localhost:5173/
  • Web origins: http://localhost:5173

これでClientの作成は完了です。

userの作成

管理画面左側のメニューからUsersをクリックし、以下の通りユーザー情報を入力します

  • Email: example@example.com
  • First name: Test
  • Last name: User

CredentialタブからSet passwordをクリック

適当なパスワードを入力して、Temporaryのトグルはオフにした状態でSaveをクリックします

これでUserの作成は完了です。

P.24 React側の設定内容確認

React側の設定として、react-oidcの用意しているOidcProviderというコンポーネントにconfigurationを渡してあげます。

  • client_id は先ほどKeycloakでClientを作成した時のものと合致するように設定します。
  • redirect_uri はreact-oidcのデフォルトのリダイレクトURIである window.location.origin + "/authentication/callback" を設定します。
  • scope はOpenID Connectなので openid は必須で、その他は取得する情報に合わせて設定します。
  • authority はKeycloakで作成したrealmのディスカバリーエンドポイント(http://localhost:8080/realms/myrealm/.well-known/openid-configuration)からissuerの値を確認し設定します。

以下はService Workerを使用する場合の設定です。今回は使用する設定にしたいので以下のように設定します。

  • service_worker_relative_url は /OidcServiceWorker.jsを設定します。
  • service_worker_only は true を設定します。

実際のApp.tsxは以下のようになります。

※Header, Contentsなどのコンポーネントは省略

/src/App.tsx

import { OidcProvider } from "@axa-fr/react-oidc";
import Header from "./AppHeader";
import Contents from "./AppContents";
import Processing from "./Processing";
import { ChakraProvider } from "@chakra-ui/react";

const configuration = {
  client_id: "myclient",
  redirect_uri: window.location.origin + "/authentication/callback",
  scope: "openid profile email offline_access",
  authority: "http://localhost:8080/realms/myrealm",
  service_worker_relative_url:'/OidcServiceWorker.js',
  service_worker_only:true,
};

const App = () => (
  <ChakraProvider>
    <OidcProvider
      configuration={configuration}
      authenticatingComponent={Processing("ログインページにリダイレクトします")}
      callbackSuccessComponent={Processing(
        "認証完了。アプリケーションにリダイレクトします"
      )}
    >
      <Header />
      <Contents />
    </OidcProvider>
  </ChakraProvider>
);

export default App;

また、OidcTrustedDomains.js にKeycloakのドメインを追加します。

/public/OidcTrustedDomains.js

const trustedDomains = {
  default: ["http://localhost:8080"],
};

React側の設定は以上となります。

P.27 リクエストの動きを見てみよう(認可リクエスト)

作成したデモアプリを使って、リクエストの動きを見ていきます。

認可コードフローの認可リクエストを送信している箇所(Keycloakにリダイレクトしている箇所)を見てみると以下のようにリクエストが送信されていることがわかります。 キャプチャだと見えづらいですが、Keycloakの認可エンドポイントに対して以下のパラメータを付与してリクエストが送信されています。

  • client_id: myclient
  • redirect_uri: http://localhost:5173/authentication/callback
  • scope: openid profile email offline_access
  • response_type: code
  • state: rmA0BoYoo0J1qGED
  • nonce: odD5RmYZEl1p
  • code_challenge: DPzlx2hhRJOfa-PmoTXl8FokmM-QZpXb1xReEglWSN8
  • code_challenge_method: S256

P.29 リクエストの動きを見てみよう(認可コードレスポンス)

認可コードフローの認可レスポンスが送られてきた箇所(Keycloakでログイン処理後にリダイレクトされている箇所)を見てみると以下のようにリダイレクトされていることがわかります。

以下がリダイレクトされたリクエストです。codeという値がパラメータに含まれているのがわかります。これが認可コードです。

P.31 リクエストの動きを見てみよう(トークンリクエスト)

先ほど認可コードが取得できたので、認可コードを付与してトークンリクエストが送られている箇所を見てみます。 以下キャプチャ画像では、パラメータを付与してトークンリクエストが送信されています。

  • code: 先ほど取得した認可コード
  • grant_type: authorization_code
  • client_id: myclient
  • redirect_uri: http://localhost:5173/authentication/callback
  • code_verifier: {code_verifierの値}

レスポンスを見てみると、アクセストークン、リフレッシュトークン、IDトークンが送信されてきているのがわかります。

P.33 リクエストの動きを見てみよう(UserInfoリクエスト)

UserInfoエンドポイントへのリクエストを見てみます。

先ほど取得したアクセストークンをAuthorization Headerに付与してリクエストが送信されています。

レスポンスを見てみると、ユーザー情報が取得できているのがわかります。

これのおかげでReactの画面上にemailなどの情報を表示できているというわけですね。

ここまでで認可コードフローのリクエストの確認は完了です!

最後にstateとnonceの検証の箇所を見てみましょう。

P.35 リクエストの動きを見てみよう(stateの検証)

認可コードフロー上でstateの検証を行なっている箇所がありますが、stateの値が一致しないとどうなるのでしょうか Keycloakから認可レスポンスが返ってきたところでstateの検証を行なっているので、ブレイクポイントを貼ってstateの値を上書きしてみます。

ご想像の通りエラーとなります。

P.37 リクエストの動きを見てみよう(nonceの検証)

nonceの値も上書きしてみます。 こちらはKeycloakからIDトークンが返ってきたあと、IDトークンの検証を行なっている箇所にブレイクポイントを貼ってnonceの値を上書きしてみます。

こちらもご想像の通りエラーとなります。

当たり前なのですが、stateやnonceの値の整合性がチェックされていて、合致しないとエラーになることがわかりました。

まとめ

OpenID Connectは難しいとよく耳にしますが、実際に手元で動かしながらどのパラメータがどういう風に生成・検証されているかを見ていくと理解が進むかと思い、今回のテーマを選ばせていただきました。

今回は基本的な部分のお話しに終始し、肝心のIDトークンの内容や検証についての話はほとんどできなかったので、またの機会があればもう少し突っ込んだ内容をお話ししてみたいと思っています!

今回の発表がみなさまのお役に立てば幸いです。