IBM Cloud から提供されている IDaaS サービスである AppID を使っています。AWS の Cognito や Auth0 のようなユーザーログインの(オンラインサインアップやオンラインパスワード変更、多要素認証なども含めた)機能全般をまとめて提供するサービスです。IBM Cloud のライトアカウントを使って無料枠内で利用することも可能です。単なるログイン機能程度であれば自分で実装してもいいのですが、オンラインサインアップや多要素認証まで考慮すると実装は面倒だし、(実在しないアドレスを使うこともできますが)メールアドレス含めて管理するのは個人情報管理の観点からも、特に試験的/実験的な段階では可能であれば避けたいという運用側の事情もあるため、こういったマネージドサービスを使うメリットは大きいと考えています。事実、自分は公私でよく使っています。

で、この AppID を使ってサービス開発をしている中で「ユーザーを無効化したい」という意見をいただきました。実験的な意味合いのあるサービスを作って、ある特定の期間だけはそのサービスを使ってもらい、その期間が完了した後は使わせたくない。でもユーザーを削除してしまうと、何かの際に再度ログインしてもらうこともできなくなったり、再作成しても ID のデータ紐付けが切れてしまってたまったデータとの関連が切れてしまうのでそれは都合が悪い。そのため「いったん無効化してログインできないようにしたい」という、運用上ごもっともな意見でした。
「ユーザーの無効化ね。まあ機能的に用意されてるんだろうな」と楽観視していたのですが、AppID のダッシュボードを調べてみると、どうも「ダッシュボード画面からはユーザーの削除はできるが無効化はできなそう」でした。おっと、これはまずいぞ。。。

このユーザー無効化を実現するためのワークアラウンドとして考えたのが「パスワードの強制変更」でした。要は管理者の権限で該当ユーザーのパスワードを強制的に変更することで事実上ログインできなくしてしまう、という方法です。アカウントは残るのでデータやその紐付けも消えることはなく、再度初期パスワードに変更すれば、万が一の時にもう一度ログインしてもらうこともできるようになる、と考えました。
で、じゃあダッシュボードからどのようにパスワードを強制変更するのかというと・・・実はパスワードの強制変更もダッシュボードからできるわけではありません。2021年8月現在、AppID ダッシュボードからはユーザーを削除することはできますが、ユーザーを無効化することも、パスワードを強制変更することもできません。
ただ、AppID SDK にはパスワードを強制変更する API がありました(文字通りの「無効化」は SDK からでもできなさそう・・):
https://www.npmjs.com/package/ibmcloud-appid

(↑このページ内の "Set User New Password" 欄参照)
つまり AppID SDK のこの setUserNewPassword 関数を使って自分でプログラムを作れば、ユーザーのパスワードを強制的に変更すること自体は実現できそうでした。それによって実質的な無効化も可能にできるのでは・・と考え、挑戦してみました。以下はその成果の共有です。
【サンプルソースコードの紹介】
AppID のユーザーパスワードを変更するサンプルの Node.js アプリケーションを作って、ソースコードを公開しました:
https://github.com/dotnsf/appid_changepassword
このリポジトリを git clone するかダウンロード&展開してください。
使い方は後述しますが、まずは依存ライブラリをまとめてインストールしておいてください(この手順はソースコードを用意した後に1回だけ行ってください):
このフォルダには4つの JavaScript コードファイルが含まれていますが、それぞれ以下のような役割です(create_users_to_appid.js とか、何気に便利なツールだと自負してます):
パスワードの変更処理を行うのは change_password.js ですが、app.js や create_users_to_appid.js も含めて、この3つのファイルを動かすためにはあらかじめ settings.js に AppID の接続情報を記載しておく必要があります。以下、一通りの使い方を紹介します。
【サンプルソースコードを動かすための事前設定項目】
このアプリを動かすには、何はともあれ App ID のインスタンスが必要です。IBM Cloud にログインしてダッシュボードから App ID を選択してリソースインスタンスを作成してください。その際に「ライト」プランを選択すると 1000 ユーザー&(一ヶ月)1000 ログインまで無料で利用することが可能です(ログインアカウントがライトアカウントの場合は、ライトプランしか選択できません):

作成した AppID インスタンスの「サービス資格情報」から(必要であれば1つ作成した上で)サービス資格情報を選択して、資格情報の内容を確認します:

資格情報の中は以下のような JSON フォーマットのテキストになっています:
この中の以下5つの情報が必要です:
・apikey
・secret
・clientId
・tenantId
・region(profilesUrl の "https://" と ".appid.cloud.ibm.com" の間の文字列(上例だと "jp-tok"))
これら5つの値を見つけたら、settings.js 内の該当する変数値として入力し、保存します:
準備の最後に App ID のコールバック URL を登録します。今回のアプリケーションはローカルホストで動かす想定をしていて、その場合はローカルホストで動かす際の URL を事前に AppID に登録しておく必要があります。
IBM Cloud ダッシュボードで作成した App ID を開き、「認証の管理」、「認証設定」を選択して、「Web リダイレクト URL の追加」から "http://localhost:8080/appid/callback" を追加します(ローカルホストで動かすのではなく、公開アドレスで動かす場合はその URL を使って "http(s)://ホスト名/appid/callback" を追加してください):

これでアプリケーションを動かす準備ができました。
【サンプルソースコードを使ってユーザーを登録】
このブログエントリの目的は「AppID のユーザーパスワードを変更する方法の紹介」です。そのため実際にログインできるユーザーを使って動作確認する必要がありますが、まずはそのユーザーを登録する必要があり、そのためのツールを紹介します。 既に AppID にユーザーが登録されていて、そのユーザーのパスワードを変更してもいい場合、ここは無視しても構いません。
まずは登録するユーザーを以下のようなフォーマットの CSV ファイルで用意します(newusers.csv 参照):
1列目のユーザー表示名はログインしたユーザーをアプリケーション内で表示する際に表示される文字列です。AppID の場合、ここはなぜか「8文字以上」という制約があるので、8文字以上の表示名を指定します。
2列目のユーザーIDは「メールアドレス形式のログインID」です。App ID の設定によってはオンラインでパスワードリセットを行ったりすることも可能で、その場合のメール送信先にもなるものです(オンラインパスワードリセットを無効にして運用する場合は実在しないメールアドレスでも構いません)。
3列目のユーザーパスワードは、2列目の ID でログインするユーザーのログイン時に指定するパスワードです。 この3列を一組とした行を必要なユーザーぶんだけ用意します。 なおサンプルで newusers.csv というファイルが含まれていて、その内容は以下のような2行になっています(以下、このファイルを使って紹介しますが、自分でこのファイルに相当する内容のファイルを用意した場合は、そのファイル名に置き換えて読み進めてください):
ではこのファイルを使って AppID にユーザーを登録します。コマンドの最後に CSV ファイル名を指定して、以下のように実行します:
画面に色々出力された後にプロンプトに戻ります。成功していると CSV ファイル内で指定されたユーザーが AppID に登録されているはずです。ダッシュボードから「クラウド・ディレクトリー」、「ユーザー」を選択し、CSV ファイルで指定したユーザーが追加されていることを確認します:

検証用のユーザーが AppID に登録できました。まずはこのユーザー ID(と CSV ファイルで指定したパスワード)でログインできるか一度確認しておきます。
【サンプルソースコードを使って登録したユーザーでログイン確認】
では今登録したユーザーが登録したパスワードでログインできるかどうかを確認するため、サンプルのウェブアプリケーションを起動します:
App ID のメッセージが数行表示された後に "server starting on 8080 ..." と表示されれば起動完了です。ウェブブラウザを開いて http://localhost:8080/ にアクセスします。すると(ログイン前なので) AppID のログイン画面に転送され、以下のような画面が表示されます:

この Email 欄と Password 欄にそれぞれユーザーのメールアドレスとパスワードを入力して "Sign in" ボタンをクリックします。先程 CSV ファイルで作成したユーザーのメールアドレスとパスワード(例えば user0001@mydomain.com と password1)を入力してみます:

正しく入力されている状態で "Sign in" ボタンをクリックするとログイン処理が成功し、画面が遷移して以下のような画面に切り替わります。ユーザーの ID、表示名、メールアドレスが表示されています。アドレスはログイン前に指定したものと同じ http://localhost:8080/ ですが、ログインが完了しているのでログイン画面には転送されず、この画面が表示されています。"Logout" ボタンでログアウトできます:

ログアウトするとログイン画面に戻ります。試しにもう1人のユーザー情報(ID: user0002@mydomain.com, パスワード: password2)でもログインしてみます:

User0002 でもログインできました。CSV ファイルから作成したユーザーで正しくログインできる所まで確認できました:

ここまで確認できたらいったんサンプルウェブアプリケーションを終了しておきます。"node app" を実行した画面で Ctrl+C を押してアプリケーションを強制終了させておきます。
【サンプルソースコードを使ってユーザーのパスワードを変更する】
さていよいよ本ブログエントリの本題となる「ユーザーパスワードの強制変更」の箇所です。change_password.js ファイルを使って以下のように実行します:
実行時に指定するパラメータは2つあります。1つ目がパスワードを変更したいユーザーのログインID(メールアドレス)、2つ目が変更後のパスワードです(つまり上の例だと user0001@mydomain.com ユーザーのパスワードを Password1 に変更します)。旧パスワードを指定せずに強制的に変更する、という点に注意してください。実行すると色々画面に表示されますが、これも処理が完了するとプロンプトに戻ります。
パスワード変更が正しく行われたかどうかを再度サンプルウェブアプリケーションで確認します:
ウェブブラウザで https://localhost:8080/ にアクセスしてログイン画面に移動したら、最初は変更前のパスワード(password1)を指定して user0001@mydomain.com でログインを試みてください。先程はログインできましたが、今はパスワードが変更されているのでログインできないはずです(メールアドレスとパスワードが一致しない、という旨のエラーメッセージが表示されます):

改めて変更後のパスワードを指定してログインすると、今度はログインが成功してログイン後の画面が表示されます:

これで AppID SDK を使ってログインパスワードを強制的に変更することができることが確認できました。
ちなみにソースコード上でのこのパスワード変更処理箇所はこのようになっています:
ibmcloud-appid ライブラリを呼び出して SelfServiceManager をインスタンス化し、(省略した部分で)アクセストークンを取得してメールアドレスで指定したユーザーの ID(上記コード上では uuid)を特定します。特定できたら selfServiceManager.setUserNewPassword( uuid, password, "en", null, null ); を実行してパスワードを変更しています(password が新しいパスワード)。3番目のパラメータは言語情報らしいですが現在使われていないらしく、既定値の "en" を指定しています。このあたり、詳しくは SDK の説明もご覧ください。
ともあれ、これで当初の目的である「App ID ユーザーの無効化」に近いオペレーションが実現できそうでした。めでたしめでたし。

で、この AppID を使ってサービス開発をしている中で「ユーザーを無効化したい」という意見をいただきました。実験的な意味合いのあるサービスを作って、ある特定の期間だけはそのサービスを使ってもらい、その期間が完了した後は使わせたくない。でもユーザーを削除してしまうと、何かの際に再度ログインしてもらうこともできなくなったり、再作成しても ID のデータ紐付けが切れてしまってたまったデータとの関連が切れてしまうのでそれは都合が悪い。そのため「いったん無効化してログインできないようにしたい」という、運用上ごもっともな意見でした。
「ユーザーの無効化ね。まあ機能的に用意されてるんだろうな」と楽観視していたのですが、AppID のダッシュボードを調べてみると、どうも「ダッシュボード画面からはユーザーの削除はできるが無効化はできなそう」でした。おっと、これはまずいぞ。。。

このユーザー無効化を実現するためのワークアラウンドとして考えたのが「パスワードの強制変更」でした。要は管理者の権限で該当ユーザーのパスワードを強制的に変更することで事実上ログインできなくしてしまう、という方法です。アカウントは残るのでデータやその紐付けも消えることはなく、再度初期パスワードに変更すれば、万が一の時にもう一度ログインしてもらうこともできるようになる、と考えました。
で、じゃあダッシュボードからどのようにパスワードを強制変更するのかというと・・・実はパスワードの強制変更もダッシュボードからできるわけではありません。2021年8月現在、AppID ダッシュボードからはユーザーを削除することはできますが、ユーザーを無効化することも、パスワードを強制変更することもできません。
ただ、AppID SDK にはパスワードを強制変更する API がありました(文字通りの「無効化」は SDK からでもできなさそう・・):
https://www.npmjs.com/package/ibmcloud-appid

(↑このページ内の "Set User New Password" 欄参照)
つまり AppID SDK のこの setUserNewPassword 関数を使って自分でプログラムを作れば、ユーザーのパスワードを強制的に変更すること自体は実現できそうでした。それによって実質的な無効化も可能にできるのでは・・と考え、挑戦してみました。以下はその成果の共有です。
【サンプルソースコードの紹介】
AppID のユーザーパスワードを変更するサンプルの Node.js アプリケーションを作って、ソースコードを公開しました:
https://github.com/dotnsf/appid_changepassword
このリポジトリを git clone するかダウンロード&展開してください。
使い方は後述しますが、まずは依存ライブラリをまとめてインストールしておいてください(この手順はソースコードを用意した後に1回だけ行ってください):
$ cd appid_changepassword $ npm install
このフォルダには4つの JavaScript コードファイルが含まれていますが、それぞれ以下のような役割です(create_users_to_appid.js とか、何気に便利なツールだと自負してます):
ファイル | 役割 |
---|---|
app.js | App ID の動作確認用ウェブアプリケーション。AppID にログイン&ログアウトするだけ |
settings.js | 動作設定用ファイル |
create_users_to_appid.js | CSV ファイルから AppID のユーザーをまとめて作成する CLI アプリ |
change_password.js | ユーザーIDと新パスワードを指定して、パスワードを強制変更する CLI アプリ |
パスワードの変更処理を行うのは change_password.js ですが、app.js や create_users_to_appid.js も含めて、この3つのファイルを動かすためにはあらかじめ settings.js に AppID の接続情報を記載しておく必要があります。以下、一通りの使い方を紹介します。
【サンプルソースコードを動かすための事前設定項目】
このアプリを動かすには、何はともあれ App ID のインスタンスが必要です。IBM Cloud にログインしてダッシュボードから App ID を選択してリソースインスタンスを作成してください。その際に「ライト」プランを選択すると 1000 ユーザー&(一ヶ月)1000 ログインまで無料で利用することが可能です(ログインアカウントがライトアカウントの場合は、ライトプランしか選択できません):

作成した AppID インスタンスの「サービス資格情報」から(必要であれば1つ作成した上で)サービス資格情報を選択して、資格情報の内容を確認します:

資格情報の中は以下のような JSON フォーマットのテキストになっています:
{ "apikey": "xxxxx", "clientId": "xxxxxxxxxxxxxxxxxxx", "secret": "xxxxxxxxxxxxxxxxxxx", "tenantId": "xxxxx", "profilesUrl": "https://jp-tok.appid.cloud.ibm.com", : : }
この中の以下5つの情報が必要です:
・apikey
・secret
・clientId
・tenantId
・region(profilesUrl の "https://" と ".appid.cloud.ibm.com" の間の文字列(上例だと "jp-tok"))
これら5つの値を見つけたら、settings.js 内の該当する変数値として入力し、保存します:
//. IBM App ID exports.region = '(region の値)'; exports.tenantId = '(tenantId の値)'; exports.apiKey = '(apikey の値)'; exports.secret = '(secret の値)'; exports.clientId = '(clientId の値)'; exports.redirectUri = 'http://localhost:8080/appid/callback'; exports.oauthServerUrl = 'https://' + exports.region + '.appid.cloud.ibm.com/oauth/v4/' + exports.tenantId;
準備の最後に App ID のコールバック URL を登録します。今回のアプリケーションはローカルホストで動かす想定をしていて、その場合はローカルホストで動かす際の URL を事前に AppID に登録しておく必要があります。
IBM Cloud ダッシュボードで作成した App ID を開き、「認証の管理」、「認証設定」を選択して、「Web リダイレクト URL の追加」から "http://localhost:8080/appid/callback" を追加します(ローカルホストで動かすのではなく、公開アドレスで動かす場合はその URL を使って "http(s)://ホスト名/appid/callback" を追加してください):

これでアプリケーションを動かす準備ができました。
【サンプルソースコードを使ってユーザーを登録】
このブログエントリの目的は「AppID のユーザーパスワードを変更する方法の紹介」です。そのため実際にログインできるユーザーを使って動作確認する必要がありますが、まずはそのユーザーを登録する必要があり、そのためのツールを紹介します。 既に AppID にユーザーが登録されていて、そのユーザーのパスワードを変更してもいい場合、ここは無視しても構いません。
まずは登録するユーザーを以下のようなフォーマットの CSV ファイルで用意します(newusers.csv 参照):
ユーザー表示名,ユーザーID,ユーザーパスワード ユーザー表示名,ユーザーID,ユーザーパスワード : :
1列目のユーザー表示名はログインしたユーザーをアプリケーション内で表示する際に表示される文字列です。AppID の場合、ここはなぜか「8文字以上」という制約があるので、8文字以上の表示名を指定します。
2列目のユーザーIDは「メールアドレス形式のログインID」です。App ID の設定によってはオンラインでパスワードリセットを行ったりすることも可能で、その場合のメール送信先にもなるものです(オンラインパスワードリセットを無効にして運用する場合は実在しないメールアドレスでも構いません)。
3列目のユーザーパスワードは、2列目の ID でログインするユーザーのログイン時に指定するパスワードです。 この3列を一組とした行を必要なユーザーぶんだけ用意します。 なおサンプルで newusers.csv というファイルが含まれていて、その内容は以下のような2行になっています(以下、このファイルを使って紹介しますが、自分でこのファイルに相当する内容のファイルを用意した場合は、そのファイル名に置き換えて読み進めてください):
User0001,user0001@mydomain.com,password1 User0002,user0002@mydomain.com,password2
ではこのファイルを使って AppID にユーザーを登録します。コマンドの最後に CSV ファイル名を指定して、以下のように実行します:
$ node create_users_to_appid newusers.csv
画面に色々出力された後にプロンプトに戻ります。成功していると CSV ファイル内で指定されたユーザーが AppID に登録されているはずです。ダッシュボードから「クラウド・ディレクトリー」、「ユーザー」を選択し、CSV ファイルで指定したユーザーが追加されていることを確認します:

検証用のユーザーが AppID に登録できました。まずはこのユーザー ID(と CSV ファイルで指定したパスワード)でログインできるか一度確認しておきます。
【サンプルソースコードを使って登録したユーザーでログイン確認】
では今登録したユーザーが登録したパスワードでログインできるかどうかを確認するため、サンプルのウェブアプリケーションを起動します:
$ node app
App ID のメッセージが数行表示された後に "server starting on 8080 ..." と表示されれば起動完了です。ウェブブラウザを開いて http://localhost:8080/ にアクセスします。すると(ログイン前なので) AppID のログイン画面に転送され、以下のような画面が表示されます:

この Email 欄と Password 欄にそれぞれユーザーのメールアドレスとパスワードを入力して "Sign in" ボタンをクリックします。先程 CSV ファイルで作成したユーザーのメールアドレスとパスワード(例えば user0001@mydomain.com と password1)を入力してみます:

正しく入力されている状態で "Sign in" ボタンをクリックするとログイン処理が成功し、画面が遷移して以下のような画面に切り替わります。ユーザーの ID、表示名、メールアドレスが表示されています。アドレスはログイン前に指定したものと同じ http://localhost:8080/ ですが、ログインが完了しているのでログイン画面には転送されず、この画面が表示されています。"Logout" ボタンでログアウトできます:

ログアウトするとログイン画面に戻ります。試しにもう1人のユーザー情報(ID: user0002@mydomain.com, パスワード: password2)でもログインしてみます:

User0002 でもログインできました。CSV ファイルから作成したユーザーで正しくログインできる所まで確認できました:

ここまで確認できたらいったんサンプルウェブアプリケーションを終了しておきます。"node app" を実行した画面で Ctrl+C を押してアプリケーションを強制終了させておきます。
【サンプルソースコードを使ってユーザーのパスワードを変更する】
さていよいよ本ブログエントリの本題となる「ユーザーパスワードの強制変更」の箇所です。change_password.js ファイルを使って以下のように実行します:
$ node change_password user0001@mydomain.com Password1
実行時に指定するパラメータは2つあります。1つ目がパスワードを変更したいユーザーのログインID(メールアドレス)、2つ目が変更後のパスワードです(つまり上の例だと user0001@mydomain.com ユーザーのパスワードを Password1 に変更します)。旧パスワードを指定せずに強制的に変更する、という点に注意してください。実行すると色々画面に表示されますが、これも処理が完了するとプロンプトに戻ります。
パスワード変更が正しく行われたかどうかを再度サンプルウェブアプリケーションで確認します:
$ node app
ウェブブラウザで https://localhost:8080/ にアクセスしてログイン画面に移動したら、最初は変更前のパスワード(password1)を指定して user0001@mydomain.com でログインを試みてください。先程はログインできましたが、今はパスワードが変更されているのでログインできないはずです(メールアドレスとパスワードが一致しない、という旨のエラーメッセージが表示されます):

改めて変更後のパスワードを指定してログインすると、今度はログインが成功してログイン後の画面が表示されます:

これで AppID SDK を使ってログインパスワードを強制的に変更することができることが確認できました。
ちなみにソースコード上でのこのパスワード変更処理箇所はこのようになっています:
var SelfServiceManager = require( 'ibmcloud-appid' ).SelfServiceManager; : : selfServiceManager.setUserNewPassword( uuid, password, "en", null, null ).then( function( user ){ resolve( user ); }).catch( function( err ){ reject( err ); }); : :
ibmcloud-appid ライブラリを呼び出して SelfServiceManager をインスタンス化し、(省略した部分で)アクセストークンを取得してメールアドレスで指定したユーザーの ID(上記コード上では uuid)を特定します。特定できたら selfServiceManager.setUserNewPassword( uuid, password, "en", null, null ); を実行してパスワードを変更しています(password が新しいパスワード)。3番目のパラメータは言語情報らしいですが現在使われていないらしく、既定値の "en" を指定しています。このあたり、詳しくは SDK の説明もご覧ください。
ともあれ、これで当初の目的である「App ID ユーザーの無効化」に近いオペレーションが実現できそうでした。めでたしめでたし。
コメント