RubyでGoogle Cloud Identity Platformのパスワードポリシーを設定する
こんにちは、エンジニアの加藤(@tomo_k09)です。
PharmaXの一部サービスでは、Google CloudのIdentity Platformのマルチテナンシーの機能を使い、テナントごとにユーザー管理を行っています。
Identity Platformのマルチテナンシー機能を使うことによって、テナントごとにパスワードポリシーの設定が可能です。
この記事では、Google Cloud Identity Platform(以下Identity Platform)のマルチテナンシー機能を利用しているアプリケーションにおけるパスワードポリシーの設定処理をRubyで実装する方法について紹介します。
本記事の前提
- Identity Platformでパスワード認証を行っていること
- Identity Platgormのマルチテナンシー機能を使っていること
実行環境
- Ruby 3.3
- Ruby on Rails 7.1.3.2
- google-apis-identitytoolkit_v3 0.14.0
PharmaXにおけるIdentity Platformによる認証について
前述の通り、PharmaXではマルチテナンシー構成を採用しており、テナントごとに独立したユーザー管理をしています。
Identity Platformによるユーザー認証を行なっているのはバックエンドのRuby on Railsです。RailsからIdentity Platformへのアクセスには、google-apis-identitytoolkit_v3というgemを使用しています。
パスワードポリシーの設定処理を実装したい背景
パスワードポリシーの設定処理を実装したい背景としては、Identity Platformのデフォルトのパスワードポリシーだとパスワードの強度が弱いためです。
しかし、Rubyにはパスワードポリシーを変更するためのSDKが提供されていません。
ただ、パスワードポリシーを更新するREST APIがGoogleから提供されているため、このAPIを使用してパスワードポリシーを更新する方針としました。
REST APIでパスワードポリシーを設定する手順
RubyでREST APIを叩いて、パスワードポリシーの設定をするには、以下のステップで準備を進めます。
サービスアカウントキーを作成する
まだサービスアカウントを作成していない場合は、以下の手順に沿ってサービスアカウントキーを作成してください。
サービスアカウントはAPIリクエストに必要なアクセストークンを取得するために使用します。
今回の実装でいうと、Identity Platformのパスワードポリシーをプログラム的に更新する場合、サービスアカウントを使用して必要な認証情報とアクセストークンを安全に管理し、APIの呼び出しを行います。
パスワードポリシーを設定する
次にパスワードポリシーを設定します。
パスワードポリシーは以下の項目を設定可能です。
<設定可能なポリシー>
-
minPasswordLength(integer)
- 最小パスワード長
- 6文字から30文字で設定可能
-
maxPasswordLength(integer)
- 最大パスワード長
- デフォルトのmax lengthはなし
-
containsLowercaseCharacter(boolean)
- 設定するパスワードに小文字は必須か
-
containsUppercaseCharacter(boolean)
- 設定するパスワードに大文字は必須か
-
containsNumericCharacter(boolean)
- 設定するパスワードに数字は必須か
-
containsNonAlphanumericCharacter(boolean)
- 設定するパスワードに英数字以外の文字は必須か
ここはお好きなようにカスタマイズしてください。
リファレンスはこちらです。
Rubyでパスワードポリシーを更新するAPIを叩く
まずは完成系のコードを紹介します。
IdentityPlatform.update_password_policy(tenant_name: your_tenant_name, tenant_id: your_tenant_id)
のようにメソッドを呼び出せば、パスワードポリシーを設定できます。
class IdentityPlatform
class << self
DEFAULT_PASSWORD_POLICY = {
minPasswordLength: 8,
containsLowercaseCharacter: true,
containsUppercaseCharacter: true,
containsNumericCharacter: true,
containsNonAlphanumericCharacter: true
}.freeze
def credentials
Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: StringIO.new(ENV.fetch("FIREBASE_ADMIN_SDK_CREDENTIALS", {})),
scope: [
"https://www.googleapis.com/auth/identitytoolkit"
].join(" ")
)
end
def update_password_policy(tenant_name, tenant_id)
project_id = JSON.parse(ENV.fetch("FIREBASE_ADMIN_SDK_CREDENTIALS", {}))["project_id"]
access_token = credentials.fetch_access_token!["access_token"]
uri = URI.parse("https://identitytoolkit.googleapis.com/v2/projects/#{project_id}/tenants/#{tenant_id}")
request = Net::HTTP::Patch.new(uri)
request.content_type = "application/json"
request["Authorization"] = "Bearer #{access_token}"
request.body = JSON.dump(
{
displayName: tenant_name,
passwordPolicyConfig: {
passwordPolicyVersions: {
customStrengthOptions: DEFAULT_PASSWORD_POLICY
}
}
}
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
response.body
end
end
end
次に簡単にではありますが、コードの解説をしていきます。
パスワードポリシーを以下のコードで指定しています。
ここはお好きなように設定してください。
class IdentityPlatform
DEFAULT_PASSWORD_POLICY = {
minPasswordLength: 8,
containsLowercaseCharacter: true,
containsUppercaseCharacter: true,
containsNumericCharacter: true,
containsNonAlphanumericCharacter: true
}.freeze
end
今回は
- 最小パスワード長を8文字
- 小文字必須
- 大文字必須
- 数字必須
- 英数字以外必須
というパスワードポリシーを設定しています。
def credentials
Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: StringIO.new(ENV.fetch("FIREBASE_ADMIN_SDK_CREDENTIALS", {})),
scope: [
"https://www.googleapis.com/auth/identitytoolkit"
].join(" ")
)
end
このメソッドは、GoogleのAPIへのアクセスに必要な認証情報(サービスアカウントクレデンシャル)を生成します。
環境変数にはFIREBASE_ADMIN_SDK_CREDENTIALSにはサービスアカウントキーを設定してください。
環境変数FIREBASE_ADMIN_SDK_CREDENTIALSからJSON形式のキーを読み取り、Google::Auth::ServiceAccountCredentials.make_credsを使って認証情報を作成します。
認可のスコープとして、Identity Toolkit API https://www.googleapis.com/auth/identitytoolkit
のみを指定しています。ちなみに、このscopeにFirebaseの各種サービスを指定して利用可能です。
def update_password_policy(tenant_name, tenant_id)
project_id = JSON.parse(ENV.fetch("FIREBASE_ADMIN_SDK_CREDENTIALS", {}))["project_id"]
access_token = credentials.fetch_access_token!["access_token"]
uri = URI.parse("https://identitytoolkit.googleapis.com/v2/projects/#{project_id}/tenants/#{tenant_id}")
request = Net::HTTP::Patch.new(uri)
request.content_type = "application/json"
request["Authorization"] = "Bearer #{access_token}"
request.body = JSON.dump(
{
displayName: tenant_name,
passwordPolicyConfig: {
passwordPolicyVersions: {
customStrengthOptions: DEFAULT_PASSWORD_POLICY
}
}
}
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
response.body
end
このメソッドは、特定のテナントのパスワードポリシーを更新するためのAPIを呼び出します。テナント名とテナントIDを渡してこのメソッドを呼び出すことによって、テナントごとにパスワードを設定できるようになります。
まず環境変数からプロジェクトIDを取得し、URIを組み立てます。Net::HTTP::PatchでPATCHリクエストを作成し、JSON形式のリクエストボディに更新データを設定します。
リクエストヘッダーにAuthorizationでアクセストークンを設定し、APIに送信します。
そして、APIからのレスポンスボディを受け取るようにしています(これはデバック用です)。
IdentityPlatform.update_password_policy(tenant_name: your_tenant_name, tenant_id: your_tenant_id)
のようにメソッドを呼び出せば、パスワードポリシーを設定できるはずです。
そのほか知っておくと便利な設定
上記のようにパスワードポリシーを設定しても、すでに強度の弱いパスワードでログインできているユーザーはログインできてしまいます。
パスワードポリシーに準拠していないユーザーにパスワードポリシーを強制するには、以下の設定をrequestのbodyに追加してください。
request.body = JSON.dump(
{
displayName: tenant_name,
passwordPolicyConfig: {
passwordPolicyVersions: {
# -----------------------
# 追加
passwordPolicyEnforcementState: "ENFORCE",
forceUpgradeOnSignin: true,
# ------------------------
customStrengthOptions: DEFAULT_PASSWORD_POLICY
}
}
}
passwordPolicyEnforcementState: "ENFORCE"と設定することにより、パスワードポリシーが強制されます。
そしてforceUpgradeOnSignin: trueと設定することによって、新しいパスワードポリシーに準拠していないユーザーがサインする時は再設定を強制されるようになります。
終わりに
今回はIdentity Platformでマルチテナンシー構成を採用している場合のパスワードポリシーの設定方法を紹介しました。参考になれば幸いです!
PharmaXでは、様々なバックグラウンドを持つエンジニアをお待ちしております。
もし興味をお持ちの場合は、私のXアカウント(@tomo_k09)までお気軽にメッセージをいただけますと幸いです。まずはカジュアルにお話しましょう!
PharmaXエンジニアチームのテックブログです。エンジニアメンバーが、PharmaXの事業を通じて得た技術的な知見や、チームマネジメントについての知見を共有します。 PharmaXエンジニアチームやメンバーの雰囲気が分かるような記事は、note(note.com/pharmax)もご覧ください。
Discussion