🤫

RubyでGoogle Cloud Identity Platformのパスワードポリシーを設定する

2024/04/19に公開

こんにちは、エンジニアの加藤(@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を使用してパスワードポリシーを更新する方針としました。

https://cloud.google.com/identity-platform/docs/reference/rest/v2/projects.tenants#PasswordPolicyConfig

REST APIでパスワードポリシーを設定する手順

RubyでREST APIを叩いて、パスワードポリシーの設定をするには、以下のステップで準備を進めます。

サービスアカウントキーを作成する

まだサービスアカウントを作成していない場合は、以下の手順に沿ってサービスアカウントキーを作成してください。
https://cloud.google.com/identity-platform/docs/install-admin-sdk?hl=ja

サービスアカウントは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テックブログ

Discussion