本記事で試すClient Initiated Backchannel Authentication (CIBA)について
- CIBAの基本的な知識は本記事では解説しておりません。別途インターネット上の記事などをご参照ください。
- 本記事ではPollモードを試します。ただし本来行うべき定期的にリクエストを送るPollingを行わず、都度手動でcurlを使ってリクエストを出します。
- Keycloakバージョン13.0.0以降で利用できます。
- 本記事の手順は、Keycloakバージョン15.0.0で検証しています。
CIBAに登場するのは以下の4つです。
- クライアント
- 認可を受けて何かをします。WEBアプリだったりIoTデバイスだったりするかもしれません。
- 本記事ではクライアントのプログラムは用意せず、curlコマンドを利用します。
- 認可を受けると「アクセストークン」がもらえて、クライアントはこのアクセストークンを使って何かをしますが、本記事ではアクセストークンをもらうところまでです。
- 認可サーバ
- クライアントから認可のリクエストを受け、ユーザに認可を求め、ユーザが認可を承認または拒否した結果をクライアントに伝えます。
- 本記事では、Keycloakが担います。
- 認証デバイス / Authentication Device (AD)
- クライアントから認可のリクエストが来たときにユーザに認可を与えてもらうためのデバイスなりサイトなりです。
- ADがどんなものなのかは、CIBAでは一部を除き定義されておりません。
- 本記事では、適当なPHPアプリケーションを用意しますが、認証すらしない実装なので実用的ではありません、学習用です。
- ユーザ
- 認証デバイスを操作して、クライアントに対して認可を与える人間です。
- 本記事では、curlを手動で実行します。
Keycloakのインストール・セットアップ
本記事ではKeycloakがインストール済みで、管理者アカウントでKeycloakの管理コンソールにアクセスできる前提で進めます。
CIBAの有効化設定
-
Keycloakに内蔵されているAuthentication Channel Providerを有効化します。
/opt/jboss/keycloak/bin/jboss-cli.sh -c /subsystem=keycloak-server/spi=ciba-auth-channel/:add(default-provider=ciba-http-auth-channel) /subsystem=keycloak-server/spi=ciba-auth-channel/provider=ciba-http-auth-channel:add(enabled=true,properties={httpAuthenticationChannelUri=${env.AUTHENTICATION_ENTITY_URL}}) exit
AD(認証デバイス)相当のPHPプログラムを適当なWEBサーバ上に構築
-
PHPが動作可能なHTTPサーバを用意します。
- 構築手順は割愛します。
-
PHP製の簡易的なAuthentication Device(AD)のプログラムを用意します。
vi /var/www/html/AD.php
<?php #KeycloakからのPOSTリクエストに含まれるAuthorizeの情報をとりあえずログに出力する(あとで使う) $headers = getallheaders(); $bearer = $headers['Authorization']; error_log($bearer, 0); #Keycloakからのリクエストに対して201レスポンスを返す(201を返すのはCIBAの仕様) header('Content-Type: application/json; charset=UTF-8'); header( "HTTP/1.1 201 Created" ); ?>
- 認可サーバ(Keycloak)から認証デバイスへリクエストが届くと、Keycloakに認証成否を通知するときに必要となるAuthenticateヘッダの内容が以下のログに出力されるはずです。(CentOS7、Apache httpdサーバで検証)
tail -f /var/log/httpd/error_log
-
CIBAの仕様では、Keycloakから認証リクエストを受け取ったら201を返す仕様なので、201が返ることを確認します。
curl "http://<認証デバイス用サーバのIPアドレスorホスト名>/AD.php" -o /dev/null -w '%{http_code}\n' -s
AD(認証デバイス)のURLをKeycloakのAuthentication Channel Providerに設定
-
Keycloakサーバの環境変数を設定します。
AUTHENTICATION_ENTITY_URL=http://<認証デバイス用サーバのIPアドレスorホスト名>/AD.php export AUTHENTICATION_ENTITY_URL printenv | grep AUTHENTICATION_ENTITY_URL
-
確認
/opt/jboss/keycloak/bin/jboss-cli.sh -c /subsystem=keycloak-server/spi=ciba-auth-channel/provider=ciba-auth-channel:read-attribute(name=properties.httpAuthenticationChannelUri) exit
-
環境変数を使わず直に指定するのもあり
/subsystem=keycloak-server/spi=ciba-auth-channel/provider=ciba-http-auth-channel:add(enabled=true,properties={httpAuthenticationChannelUri="http://<認証デバイス用サーバのIPアドレスorホスト名>/AD.php"})
-
Keycloak管理画面上の設定
- 任意のレルムを作成します。
- 手順割愛
- 任意のユーザを作成します。
- 手順割愛
- CIBA用のOpenID Connectクライアントを作成します。
- レルム > Clients > Createボタンを押下します。
- 「Client ID」に任意の名前を入力し、Saveボタンを押下します。
- 以下の設定を変更します。
- Access Type
public
→confidential
- ※CIBAは、仕様によりクライアント認証が必要なため、publicでは利用できません。
- OIDC CIBA Grant Enabled
OFF
→ON
- Standard Flow Enabled
ON
→OFF
- ※Standard Flowを利用しないのであれば、これを無効にすることで、CIBAに不要な「redirect_uri」パラメータの設定が不要になります。
- Access Type
CIBAの実行手順
-
クライアントIDとクライアントシークレットを「:」で連結した文字列をBase64エンコードして、クライアント認証情報を作成します。
echo -n '<クライアントID>:<クライアントシークレット>' | openssl base64
- クライアントIDの確認場所
- レルム > Clients > 設定したクライアント > Client ID
- クライアントシークレットの確認場所
- レルム > Clients > 設定したクライアント > Credentialsタブ > Secret
- クライアントIDの確認場所
-
クライアント(本記事ではcurl)がKeycloakのバックチャンネル認証エンドポイントにリクエストを出します。
curl -X POST \ -H "Content-Type:application/x-www-form-urlencoded" \ -H "Authorization:Basic <クライアント認証情報>" \ -d "scope=openid%20email" \ -d "login_hint=<Keycloakに登録されているユーザ名>" \ -d "binding_message=hogefuga" \ 'http://localhost:8080/auth/realms/<レルム名>/protocol/openid-connect/ext/ciba/auth' \ | jq
- リクエストを受け取ったKeycloakは、裏(バックチャネル)で認証デバイスに認可リクエストを出します。
- PHPのログに出力されていることを確認します。出力されていない場合は、環境に合わせて別のログファイルを確認、PHPのソースコードを修正してください。
tail -f /var/log/httpd/error_log
-
ユーザが認証デバイスで認証し、Keycloakに認可成否を伝えます。(本来は認証デバイスがKeycloakに認可成否のリクエストを出しますが、今回は手動でcurlで出します)
curl -X POST \ -H "Content-Type:application/json" \ -H "Authorization:Bearer <PHPがログに出力したAuthenticateヘッダの内容>" \ -d \ '{ "status":"SUCCEED" }' \ 'http://localhost:8080/auth/realms/<レルム名>/protocol/openid-connect/ext/ciba/auth/callback'
- Authorizationヘッダは、最初にクライアントがバックチャンネル認証エンドポイントにリクエストを出したときにPHPがログに出力したものを使います。
- 上記のcurlでは問答無用で認証成功の「
"status":"SUCCEED"
」を渡していますが、本来は(プッシュ通知等を利用して)認証デバイス側でユーザに認可してもらってから出すものです。どう認証するかはCIBAでは定義されていません。
-
トークン認証が通ったか確認する
curl -X POST \ -H "Content-Type:application/x-www-form-urlencoded" \ -H "Authorization:Basic <クライアント認証情報>" \ -d "grant_type=urn:openid:params:grant-type:ciba" \ -d "auth_req_id=<最初のcurlリクエストで返却されたauth_req_id>" \ 'http://localhost:8080/auth/realms/<レルム名>/protocol/openid-connect/token'
-
通らなかった時(まだ認証していない時)
{"error":"authorization_pending","error_description":"The authorization request is still pending as the end-user hasn't yet been authenticated."}
-
通らなかった時(すでに「auth_req_id」の有効期限が切れている時)
{"error":"invalid_grant","error_description":"Invalid auth_req_id"}
-
通った時 (以下は見やすくするため改行を入れています)
{ "access_token": "eyJ〜〜中略〜〜unmDQ", "expires_in": 300, "refresh_expires_in": 1800, "refresh_token": "eyJ〜〜中略〜〜psW4", "token_type": "Bearer", "not-before-policy": 0, "session_state": "959706e2-1ff5-43a3-8952-0fdf812768f0", "scope": "email profile" }
-
クライアントは、取得したアクセストークン(レスポンス内のaccess_token)を利用して何かを行います。
今後
今回はKeycloakに内蔵されている「ciba-http-auth-channel」というAuthentication Channel Providerを有効にして利用しましたが、独自のSPIを開発することも可能です。
Authentication Channel Providerがユーザに対してどのように認証認可を行うかは、CIBAの仕様で定められていません。
今回利用した「ciba-http-auth-channel」の実装ソースコードは以下にあります。
参考
- 閲覧数 924
コメントを追加