OAuthのクライアントタイプ
2種類のクライアントタイプが定義されています。
コンフィデンシャルクライアント
クレデンシャルをセキュアに保持できる、サーバーサイドに設置されるWebアプリケーションが想定されています。
パブリッククライアント
ブラウザ上で動作するJavaScriptアプリが想定されています。
クレデンシャルをセキュアに保持するのが困難なため、クレデンシャルを持たず、クライアント認証も行われません(ユーザーの認証認可は行えます)。
コンフィデンシャルクライアントよりもセキュリティーレベルが下がります。
本記事で行うこと
クレデンシャルは、OAuthではクライアントシークレットと呼ばれており、本記事では以後client_secretと表記します。ブラウザ側にclient_secretを持たせてはいけません。
-
client_secretをサーバーサイドにセキュアに保持できる前提で、Next.jsアプリでコンフィデンシャルクライアントを試します。
-
サーバーアプリを使用しない前提で、Reactアプリでパブリッククライアントを試します。
環境
- httpsで行うべきところをhttpで行っています。
- KeycloakはWindows11のWSL(Ubuntu)上のDockerで起動しました。
- 同じくWSLのNode.jsを使用して、ReactやNext.jsアプリを起動しました。
- Windows11のブラウザを使用しました。
- ホスト名はすべてlocalhostになります。
localhostでのPORT一覧
- Keycloak:8080
- Next.jsアプリ:3000
- Reactアプリ:5173
Keycloakの導入と設定
本記事では、OpenIDプロバイダとしてKeycloakを使用します。
Keycloakの導入
Keycloak公式サイトに記載されている、docker runコマンドを起動します。
ID・パスワード共にadminとなります。
本記事の執筆時点で、Keycloakのバージョンは21.1.1でした。
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:21.1.1 start-dev
Keycloakの管理コンソールにサインイン
- http://localhost:8080/ にアクセスし、「Administration Console」をクリックします。
- サインインします。
- サイトを日本語で表示させるには、左サイドメニューで「Realm settings」を選択し、「Localization」タブで日本語に設定して保存します。
本記事では、日本語化せずに作業します。
レルムを追加
動作確認用にレルムを追加し、そこで操作を行うことにします。
- 「Create Realm」ボタンをクリックします。
- 本記事ではレルム名をdemoにしましたが、何でも良いです。Createボタンをクリックしてレルムを作成します。
- レルム作成に成功し、カレントレルムがdemoに切り替わりました。
クライアントを作成
本記事では後ほど、クライアントアプリをNext.jsとReactで作成します。クライアントアプリ用にKeycloakのクライアントを作成します。
- 左サイドメニューの「Clients」をクリックし、「Create client」ボタンをクリックします。
- 「Client type」が「OpenID Connect」であることを確認します。
- 本記事では「Client ID」を「demo-client」にしましたが、何でも良いです。
- Nextボタンをクリックします。
- 画面の「Client authentication」は、クライアント認証を意味します。
- 画面の「Authorization」は、認可を意味します。
- 画面の「Standard flow」は、認可コードフローを意味し、ユーザーの認証認可が行われます。
- 「Implicit flow」と「Direct access Grants」は、セキュリティー上の理由で使うべきでないとされています。
- 「Service Accounts」はユーザーの認証認可が行われません。本記事では触れません。
- コンフィデンシャルクライアントにするには、「Client authentication(認証)」と「Authorization(認可)」を両方ONにします。
- パブリッククライアントにするには、「Client authentication(認証)」と「Authorization(認可)」を両方OFFにします。
- 本記事では先にNext.jsでコンフィデンシャルクライアントを試しますので、「Client authentication(認証)」と「Authorization(認可)」を両方ONにします。
- 「Standard flow(認可コードフロー)」を選択し、他のフローは選択を外します。
- 「Next」ボタンをクリックします。
- 後ほど、アプリ毎に「Valid redirect URIs」と、CORSのために「Web origins」を設定します。今は空白のままSaveボタンをクリックします。
- クライアント作成が完了しました。「Settings」タブを選択するとクライアント作成時に設定した内容が出てくるので、再設定できます。
- PKCE(ピクシー)を使用するのが望ましいので、設定します。
「Advanced」タブを選択し、「Advanced Settings」をクリックします。
- 「Proof Key for Code Exchange Code Challenge Method」で「S256」を選択し、「Save」ボタンをクリックします。
クライアント情報を確認
demoレルムのOIDC(OpenID Connect)のエンドポイント等の情報は、http://localhost:8080/realms/demo/.well-known/openid-configuration から取得できます。
Ubuntuの端末で以下を実行してみます。
curl http://localhost:8080/realms/demo/.well-known/openid-configuration | jq
Keycloakの管理コンソールに戻って、「Credentials」タブの内容を確認します。
- 「Client Authenticator」が「Client Id and Secret」になっていることを確認します。
- 「Client secret」は、本記事冒頭から述べてきたクライアントシークレットと呼ばれるクレデンシャルのことで、本記事ではclient_secretと表記しています。後ほどコンフィデンシャルクライアントで使用します。パブリッククライアントでは使用しません。
- 本記事のclient_secretは「r9wzsovWu8GO6QDSxH4eZ5Pduh2BSLFG」でした。本番環境でのclient_secretは取り扱い要注意で、外部に漏らしてはいけません。
「Client scopes」タブの内容を確認します。
- デモアプリとしては、Keycloakに対しユーザーの「email」「profile」あたりを要求すると良さそうです。
- 「offline_access」を要求すると、リフレッシュトークンがクライアントアプリに渡されます。
ログイン用のユーザーを追加
demoレルムにユーザーを追加します。
- 左サイドメニューのUsersをクリックし、「Create new user」ボタンをクリックします。
- 本記事ではUsernameをuser1にしましたが、何でも良いです。
- Emailに架空のメールアドレス、First nameとLast nameにテスト用の氏名を入力しました。
- Createボタンをクリックします。
- user1ユーザーが追加され、Detailsタブが表示されたら、Credentialsタブに切り替えます。
- 「Set password」ボタンをクリックします。
- ユーザーのパスワードを設定します。
- TemporaryをONにすると、初回ログイン時にパスワードを変更するよう要求されます。本記事ではOFFにしました。
作成したユーザーで、アカウント管理コンソールへログイン確認
Keycloakには、admin用の「管理コンソール」と、ユーザー用の「アカウント管理コンソール」があるそうです。
user1ユーザーでアカウント管理コンソールにログインできることを確認します。
- 管理コンソールとは別セッションでブラウザを開いて http://localhost:8080/realms/demo/account/ にアクセスし、「Sign in」ボタンをクリックします。
- ログインします。
- ログイン後のアカウント管理コンソールです。
- 本記事でのアカウント管理コンソールの出番は終了です。
Next.jsアプリでコンフィデンシャルクライアントを試す
Auth.js の GitHubリポジトリに、Next.jsのexamplesアプリ が入っていましたので、使わせていただくことにします。
- 端末で以下を実行します。
git clone https://github.com/nextauthjs/next-auth
cd next-auth/apps/examples/nextjs/
npm install
cp .env.local.example .env.local
- 参考までに、git log
$ git log
commit f62c016725bf65e80a3cf5b6c59293120492fc4e (HEAD -> main, origin/main, origin/HEAD)
Author: Balázs Orbán <info@balazsorban.com>
Date: Fri May 12 12:51:59 2023 +0200
chore: revert `picture` to `image`
(後略)
ファイル編集
- 「pages/api/auth/[...nextauth].ts」の編集
以下の内容に変更します。
import NextAuth, { NextAuthOptions } from "next-auth"
import KeycloakProvider from "next-auth/providers/keycloak"
// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
export const authOptions: NextAuthOptions = {
// https://next-auth.js.org/configuration/providers/oauth
providers: [
KeycloakProvider({
clientId: process.env.KEYCLOAK_ID,
clientSecret: process.env.KEYCLOAK_SECRET,
issuer: process.env.KEYCLOAK_ISSUER,
}),
],
callbacks: {
async jwt({ token }) {
token.userRole = "admin"
return token
},
},
}
export default NextAuth(authOptions)
- 参考までに、git diff
$ git diff
diff --git a/apps/examples/nextjs/pages/api/auth/[...nextauth].ts b/apps/examples/nextjs/pages/api/auth/[...nextauth].ts
index bec48576..bcff7e84 100644
--- a/apps/examples/nextjs/pages/api/auth/[...nextauth].ts
+++ b/apps/examples/nextjs/pages/api/auth/[...nextauth].ts
@@ -1,36 +1,15 @@
import NextAuth, { NextAuthOptions } from "next-auth"
-import GoogleProvider from "next-auth/providers/google"
-import FacebookProvider from "next-auth/providers/facebook"
-import GithubProvider from "next-auth/providers/github"
-import TwitterProvider from "next-auth/providers/twitter"
-import Auth0Provider from "next-auth/providers/auth0"
+import KeycloakProvider from "next-auth/providers/keycloak"
// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
export const authOptions: NextAuthOptions = {
// https://next-auth.js.org/configuration/providers/oauth
providers: [
- Auth0Provider({
- clientId: process.env.AUTH0_ID,
- clientSecret: process.env.AUTH0_SECRET,
- issuer: process.env.AUTH0_ISSUER,
- }),
- FacebookProvider({
- clientId: process.env.FACEBOOK_ID,
- clientSecret: process.env.FACEBOOK_SECRET,
- }),
- GithubProvider({
- clientId: process.env.GITHUB_ID,
- clientSecret: process.env.GITHUB_SECRET,
- }),
- GoogleProvider({
- clientId: process.env.GOOGLE_ID,
- clientSecret: process.env.GOOGLE_SECRET,
- }),
- TwitterProvider({
- clientId: process.env.TWITTER_ID,
- clientSecret: process.env.TWITTER_SECRET,
- version: "2.0",
+ KeycloakProvider({
+ clientId: process.env.KEYCLOAK_ID,
+ clientSecret: process.env.KEYCLOAK_SECRET,
+ issuer: process.env.KEYCLOAK_ISSUER,
}),
],
callbacks: {
- 「.env.local」の編集
NEXTAUTH_SECRET に、Ubuntu端末で「openssl rand -hex 32」を実行した結果を設定します。
$ openssl rand -hex 32
6932e07a13dc554c18e7eb84da5c9a2e9b60fc1d0be1aa0342855d21e192b8e5
KEYCLOAK_SECRETには、Keycloakが発行したclient_secretを設定します。
NEXTAUTH_SECRET と KEYCLOAK_SECRETは、本番環境では取り扱い要注意で、サーバーサイド等にセキュアに保持させる必要があります。
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=6932e07a13dc554c18e7eb84da5c9a2e9b60fc1d0be1aa0342855d21e192b8e5
KEYCLOAK_ID=demo-client
KEYCLOAK_SECRET=r9wzsovWu8GO6QDSxH4eZ5Pduh2BSLFG
KEYCLOAK_ISSUER=http://localhost:8080/realms/demo
アプリ起動
端末で「npm run dev」を実行します。
「url: http://localhost:3000 」と表示されましたので、次のステップでKeycloakに設定します。
$ npm run dev
> dev
> next
- ready started server on 0.0.0.0:3000, url: http://localhost:3000
- info Loaded env from /home/user/next-auth/apps/examples/nextjs/.env.local
- event compiled client and server successfully in 444 ms (209 modules)
- wait compiling...
- event compiled client and server successfully in 148 ms (209 modules)
Keycloakでクライアント設定
- 「demo」レルムで、左サイドメニューの「Clients」をクリックします。
- クライアント一覧が表示された時は、「demo-client」をクリックします。
- 「Settings」タブを選択し、「Access settings」をクリックします。
- 「Valid redirect URIs」に「http://localhost:3000/* 」を追加します。
- 「Web origins」に「http://localhost:3000 」を追加します。
- 「Save」ボタンをクリックします。
- 「Capability config」をクリックし、コンフィデンシャルクライアントの設定になっていることを確認します。
ブラウザでアプリ実行
- 管理コンソールとは別セッションでブラウザを開いて http://localhost:3000 にアクセスし、「Sign in」ボタンをクリックします。
- 「Sign in with Keycloak」ボタンをクリックします。
- Keycloakのユーザー認証画面が出てきました。
この画面でのブラウザのURLは以下になりました。
http://localhost:8080/realms/demo/protocol/openid-connect/auth?client_id=demo-client&scope=openid%20email%20profile&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fcallback%2Fkeycloak&state=8sYFMLKBO_7KmqYPM9FDdGAqTcGyDwNWrPk2onoze7c&code_challenge=QFj-06Bkq4Elwd5dMI7GqUD0WpAI0NwTxIbAJVBD6gY&code_challenge_method=S256
URLからクエリパラメータを取り出すと以下のようになりました。
- 「response_type=code」で、認可コードフローになっていることが確認できます。
- 「code_challenge」があるので、PKCEが使用されていることが確認できます。
client_id=demo-client
scope=openid email profile
response_type=code
redirect_uri=http://localhost:3000/api/auth/callback/keycloak
state=8sYFMLKBO_7KmqYPM9FDdGAqTcGyDwNWrPk2onoze7c
code_challenge=QFj-06Bkq4Elwd5dMI7GqUD0WpAI0NwTxIbAJVBD6gY
code_challenge_method=S256
- ログインすると、Keycloakに登録したメールアドレスが表示されました。このメールアドレスはNext.jsアプリで管理しておらず、Keycloakから取得したものです。
以上、Next.jsアプリでコンフィデンシャルクライアントを試しました。
Reactアプリでパブリッククライアントを試す
react-oidcのGitHubリポジトリに、Reactのdemoアプリ が入っていましたので、使わせていただくことにします。
- 端末で以下を実行します。
git clone https://github.com/AxaFrance/react-oidc
cd react-oidc/packages/react-vite-demo/
npm install
- 参考までに、git log
$ git log
commit e3d208f86eba26765e064db675fed2cc4f15c846 (HEAD -> master, origin/master, origin/HEAD)
Author: GitHub <github-action@bot.com>
Date: Fri May 12 16:03:32 2023 +0000
[skip ci] Update version package.json
(後略)
ファイル編集
- 「src/App.tsx」の編集
configuration変数を以下の内容に変更します。
export const configuration = {
client_id: 'demo-client',
redirect_uri: window.location.origin + '/authentication/callback',
silent_redirect_uri: window.location.origin + '/authentication/silent-callback',
// silent_login_uri: window.location.origin + '/authentication/silent-login',
scope: 'openid profile email offline_access',
authority: 'http://localhost:8080/realms/demo',
// authority_time_cache_wellknowurl_in_second: 60* 60,
refresh_time_before_tokens_expiration_in_second: 40,
//service_worker_relative_url: '/OidcServiceWorker.js',
service_worker_only: false,
// storage: localStorage,
// silent_login_timeout: 3333000
// monitor_session: true,
extras: { youhou_demo: 'youhou' },
token_renew_mode: TokenRenewMode.access_token_invalid,
};
- 参考までに、git diff
$ git diff
diff --git a/packages/react-vite-demo/src/App.tsx b/packages/react-vite-demo/src/App.tsx
index 3b78113..0d59d37 100644
--- a/packages/react-vite-demo/src/App.tsx
+++ b/packages/react-vite-demo/src/App.tsx
@@ -5,12 +5,12 @@ import './App.css'
import {OidcProvider, TokenRenewMode, useOidc} from "@axa-fr/react-oidc";
export const configuration = {
- client_id: 'interactive.public.short',
+ client_id: 'demo-client',
redirect_uri: window.location.origin + '/authentication/callback',
silent_redirect_uri: window.location.origin + '/authentication/silent-callback',
// silent_login_uri: window.location.origin + '/authentication/silent-login',
- scope: 'openid profile email api offline_access',
- authority: 'https://demo.duendesoftware.com',
+ scope: 'openid profile email offline_access',
+ authority: 'http://localhost:8080/realms/demo',
// authority_time_cache_wellknowurl_in_second: 60* 60,
refresh_time_before_tokens_expiration_in_second: 40,
//service_worker_relative_url: '/OidcServiceWorker.js',
アプリ起動
端末で「npm run dev」を実行します。
「http://localhost:5173/ 」と表示されましたので、次のステップでKeycloakに設定します。
$ npm run dev
> react-vite@0.0.0 dev
> vite
VITE v4.2.1 ready in 226 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h to show help
Keycloakでクライアント設定(その1)
- 「demo」レルムで、左サイドメニューの「Clients」をクリックします。
- クライアント一覧が表示された時は、「demo-client」をクリックします。
- 「Settings」タブを選択し、「Access settings」をクリックします。
- 「Valid redirect URIs」に「http://localhost:5173/* 」を追加してしまいます。
- 「Web origins」に「http://localhost:5173 」を追加してしまいます。
- 「Save」ボタンをクリックします。
- 「Capability config」をクリックすると、コンフィデンシャルクライアントの設定になっていました。このままアプリを実行するとログインに失敗するはずですが、それを確認したいので一旦このままにしておきます。
ブラウザでアプリ実行(その1:失敗確認)
- 管理コンソールとは別セッションでブラウザを開いて http://localhost:5173 にアクセスし、「Login」ボタンをクリックします。
- Keycloakのユーザー認証画面が出てきましたので、ログインします。
- 思惑通り、ログインに失敗しました。
Keycloakでクライアント設定(その2)
パブリッククライアントの設定をします。
- 「demo」レルムで、左サイドメニューの「Clients」をクリックします。
- クライアント一覧が表示された時は、「demo-client」をクリックします。
- 「Settings」タブを選択し、「Capability config」をクリックします。
- パブリッククライアントの設定にして、「Saveボタン」をクリックします。
ブラウザでアプリ実行(その2:成功確認)
- 別セッションのブラウザで http://localhost:5173 にアクセスし、「Login」ボタンをクリックします。
- Keycloakのユーザー認証画面が出てきました。
この画面でのブラウザのURLは以下になりました。
http://localhost:8080/realms/demo/protocol/openid-connect/auth?client_id=demo-client&redirect_uri=http%3A%2F%2Flocalhost%3A5173%2Fauthentication%2Fcallback&scope=openid%20profile%20email%20offline_access&response_type=code&youhou_demo=youhou&state=gKuE0LA91YxJPOqf&nonce=HnHnQOk6U1I6&code_challenge=DRCBrBfvleNsWeHOyE4ufGirQ_VrG3HnBOR6rcQrsks&code_challenge_method=S256
URLからクエリパラメータを取り出すと以下のようになりました。
- 「response_type=code」で、認可コードフローになっていることが確認できます。
- 「code_challenge」があるので、PKCEが使用されていることが確認できます。
- このアプリでは「nonce」も使われています。
client_id=demo-client
redirect_uri=http://localhost:5173/authentication/callback
scope=openid profile email offline_access
response_type=code
youhou_demo=youhou
state=gKuE0LA91YxJPOqf
nonce=HnHnQOk6U1I6
code_challenge=DRCBrBfvleNsWeHOyE4ufGirQ_VrG3HnBOR6rcQrsks
code_challenge_method=S256
- ログインすると、以下の画面になりました。
以上、Reactアプリでパブリッククライアントを試しました。
Next.jsのメリットがまた一つ
Next.jsはReactの機能強化版ですが、OpenID Connectの認証認可でコンフィデンシャルクライアントを、ライブラリを使用して手間をかけずに実装できるメリットがありました。