【必読】なぜ今、Aura ではなく LWR を選ぶべきなのか?

【ソーシャルログイン】Experience Cloud に Google アカウントでログインさせる完全ガイド:Apex とカスタムメタデータ型による「実務級」の実装【決定版】

A professional editorial 3D illustration symbolizing secure social authentication and cloud integration. In a dark, minimalist digital space, a sleek, glowing interface portal acts as a gateway. On one side, a vibrant prism of light representing user identity flows across a shimmering crystalline bridge into a sophisticated, structured architectural cloud. The scene is filled with subtle holographic data streams, glowing nodes, and translucent layers representing backend logic and metadata configurations. The lighting is cinematic, featuring deep blues, cyans, and warm amber accents, conveying a sense of enterprise-grade security, high-tech automation, and a seamless user experience. 基礎・アーキテクチャ

この記事はバージョン Winter ’26 において執筆しています。
現在の動作と異なる場合がありますので、ご認識おきください。

「IDとパスワードを入力して会員登録する」 このプロセスは、現代のWebユーザーにとって大きな離脱ポイントになります。ユーザーの手間を減らし、登録率を劇的に向上させるのが 「ソーシャルログイン(SSO)」 です。

Experience Cloud は標準で Google 認証に対応していますが、実務で運用するには、自動生成されるプログラム(Apex)をそのまま使うのではなく、「既存顧客との紐付け」「プロファイルの出し分け」 を考慮したカスタマイズが不可欠です。

この記事では、GCP (Google Cloud Platform) の設定から、Salesforce 認証プロバイダーの作成、そして カスタムメタデータ型 (CMDT) を活用したメンテナンス性の高い Apex 実装まで、プロフェッショナルな構築手順を完全網羅します。

LWRって何?どんなメリットがあるの?
そんな疑問を解決するにはまずは以下の記事をご覧ください。
Salesforce LWRとは? Experience Cloudの次世代ランタイムを徹底解説

仕組みの理解:OAuth と「登録ハンドラ」

設定に入る前に、裏側の動きを理解しましょう。

  1. 認証 (Auth): Google がユーザーの本人確認を行います。
  2. 認可 (Authorize): Salesforce が Google からメールアドレス等の情報を受け取ります。
  3. 登録ハンドラ (Registration Handler): ここが重要です。
    • 受け取った情報を使って、Salesforce内のユーザーを検索・作成する Apex クラスです。
    • 今回は、このクラスを 「ハードコードなし」 で設計し、後から設定変更だけで挙動を変えられるようにします。

Step 1: Google Cloud Platform (GCP) での準備

まずは Google 側の設定です。
既存のプロジェクトを流用せず、新規プロジェクト を作成することを推奨します(同意画面に正しいアプリ名を表示するため)。

  1. プロジェクト作成: Google Cloud Console で MY SITE LOGIN などの名前で作成。
  2. OAuth 同意画面:
    • User Type: 「外部 (External)」を選択。
    • アプリ情報: サイト名やロゴを設定。
    • スコープ: email, profile, openid を追加。
    • テストユーザー: 検証に使う自分の Gmail アドレスを追加。
  3. 認証情報 (OAuth クライアントID):
    • 種類: 「Web アプリケーション」を選択。
    • リダイレクトURI: 一旦空欄のまま 作成します(後で Salesforce の値を入れに戻ってきます)。
    • IDとシークレット: 生成された「クライアントID」と「クライアントシークレット」をコピーしておきます。
  • プロジェクト作成
  • OAuth 同意画面
  • テストユーザー追加
  • スコープ追加
  • クライアント追加
  • APIとサービス>OAuth同意画面
  • 開始
  • 対象は外部を選択

[更新] > [Save]

  • クライアントを作成
  • 情報入力(リダイレクトURLは空)

Step 2: Salesforce「認証プロバイダー」の設定

Salesforce 側に Google を受け入れる口を作ります。

  1. [設定] > [認証プロバイダー] > [新規] をクリック。
  2. プロバイダータイプ: Google を選択。
  3. コンシューマ鍵/秘密: Step 1 で取得した ID とシークレットを貼り付けます。
  4. 登録ハンドラ: 種別「Apex」と「登録ハンドラテンプレートを自動作成」を選択します。
  5. 保存後、生成された 「コールバック URL」 をコピーします。

💡忘れずに!GCP のコンソールに戻り、コピーしたコールバック URL を「承認済みのリダイレクト URI」に貼り付けて保存してください。これで Google と Salesforce の握手が完了です。

  • 認証プロバイダー
  • コールバックURL
  • GCPに貼り付け

Step 3: カスタムメタデータ型 (CMDT) の準備

Step2 で登録ハンドラ: 種別「Apex」と「登録ハンドラテンプレートを自動作成」を選択したことによって自動的に Apex コードが出来上がっています。このコードはテンプレートなので実際の利用前にカスタマイズする必要があります。

Apex コードの中に「プロファイル名」や「取引先名」を直接書くと、環境移行時や変更時に大変です。これらを外出しして管理するために、カスタムメタデータ型を作成します。

  1. [設定] > [カスタムメタデータ型] で新規作成。
    • 表示ラベル: Social Login Config (Social_Login_Config__mdt)
  2. カスタム項目を作成:
    • DefaultAccountName__c (テキスト255): 受け皿となる取引先名
    • ExternalProfileName__c (テキスト255): デフォルトのプロファイル名
    • VipProfileName__c (テキスト255): 特別会員用のプロファイル名
  3. レコードを作成:
    • ラベル: GoogleAuthSettings
    • 各項目に、実際の取引先名(例: Google Auth Users)やプロファイル名(例: Customer Community User)を入力します。
  • カスタムメタデータを作成
  • カスタム項目を作成
  • レコードを作成

Step 4: 登録ハンドラ (Apex) の実装【完全版】

自動生成された Apex クラスを、以下のロジックに書き換えます。(クラス名は上書きしないように注意)

Apex コードはこちらをクリック💡
/**
 * @description Googleソーシャルログイン用の登録ハンドラ (完全版・VIP対応)
 * 外部ユーザー(Community)の自動作成と紐付けを行います。
 * 設定値(プロファイル名、取引先名)はすべてカスタムメタデータ型から取得します。
 */
global class AutocreatedRegHandler1764930569603 implements Auth.RegistrationHandler {

    /**
     * @description ユーザー作成を許可するかどうかの判定
     */
    global boolean canCreateUser(Auth.UserData data) {
        return true; 
    }

    /**
     * @description 新規ユーザー作成ロジック
     * Google未連携の既存のユーザーがログインした場合、Google連携済みに更新します。
     * Contactあり / Userなし: Google から受け取った情報で User を作成してログインさせます。
     * Contactなし / Userなし: Google から受け取った情報で Contact も User も新規作成します。
     */
    global User createUser(Id portalId, Auth.UserData data) {
        if (!canCreateUser(data)) {
            return null;
        }

        // Google未連携の既存のユーザーがいれば、それを返して「紐付け」完了とする
        List<User> existingUsers = [
            SELECT Id, Email, ContactId 
            FROM User 
            WHERE Username = :data.email + '.' + UserInfo.getOrganizationId() // ユーザー名生成ルールに合わせる
            OR Email = :data.email
            LIMIT 1
        ];

        if (!existingUsers.isEmpty()) {
            System.debug('既存ユーザーが見つかりました。紐付けを行います。');
            return existingUsers[0]; // これを返すだけでリンクが作成されます
        }

        // 1. CMDTから設定値を読み込む
        Social_Login_Config__mdt config = Social_Login_Config__mdt.getInstance('GoogleAuthSettings');
        
        // デフォルト設定の取得
        String accountName = (config != null) ? config.DefaultAccountName__c : 'Google Auth Users';
        String defaultProfileName = (config != null) ? config.ExternalProfileName__c : 'Customer Community User';
        
        // ★VIP用プロファイル名もCMDTから取得
        String vipProfileName = (config != null && config.VipProfileName__c != null) ? config.VipProfileName__c : 'My Site VIP User';

        // コミュニティ経由のログインかどうか判定
        if (data.attributeMap.containsKey('sfdc_networkid')) {
            
            // 2. 既存の取引先責任者 (Contact) をメールアドレスで検索
            // これにより「既存顧客の紐付け」と「ランク判定」を同時に行います
            List<Contact> existingContacts = [
                SELECT Id, AccountId, Is_VIP__c // 判定に使いたい項目
                FROM Contact 
                WHERE Email = :data.email 
                LIMIT 1
            ];

            Contact c;
            String targetProfileName;

            if (!existingContacts.isEmpty()) {
                // A. 既存Contactが見つかった場合
                c = existingContacts[0];
                
                // ★Contactの属性を見てプロファイルを出し分ける
                if (c.Is_VIP__c == true) {
                    targetProfileName = vipProfileName;
                } else {
                    targetProfileName = defaultProfileName;
                }
                
                // ※本来はここでAccountIDの整合性チェックなどを行うとより安全です

            } else {
                // B. 見つからない場合(完全新規) -> デフォルトプロファイルを使用
                targetProfileName = defaultProfileName;
                
                // 取引先の取得・作成
                Account a;
                try {
                    a = [SELECT Id FROM Account WHERE Name = :accountName LIMIT 1]; 
                } catch (Exception e) {
                    a = new Account(Name = accountName);
                    insert a;
                }

                // 新規Contact作成
                c = new Contact();
                c.AccountId = a.Id;
                c.Email = data.email;
                c.FirstName = data.firstName;
                c.LastName = data.lastName;
                insert c;
            }

            // 3. 決定したプロファイル名で User を作成
            // バインド変数 :targetProfileName で検索
            Profile p = [SELECT Id FROM Profile WHERE Name = :targetProfileName LIMIT 1];
            
            User u = new User();
            u.Username = data.email + '.' + UserInfo.getOrganizationId();
            u.Email = data.email;
            u.LastName = data.lastName;
            u.FirstName = data.firstName;
            
            String alias = (data.firstName != null && data.firstName != '') ? data.firstName : 'guest';
            if (alias.length() > 8) { alias = alias.substring(0, 8); }
            u.Alias = alias;
            
            // 日本語環境に合わせて明示的に指定します
            u.LanguageLocaleKey = 'ja'; 
            u.LocaleSidKey = 'ja_JP';
            u.EmailEncodingKey = 'UTF-8';
            u.TimeZoneSidKey = 'Asia/Tokyo';
            
            u.ProfileId = p.Id;
            u.ContactId = c.Id; // 既存または新規のContactID
            
            return u;

        } else {
            return null;
        }
    }

    /**
     * @description 既存ユーザーの更新ロジック
     * Google連携済みのユーザーがログインするたびに、Googleの最新情報でSalesforceのレコードを更新します。
     */
    global void updateUser(Id userId, Id portalId, Auth.UserData data) {

        // ------------------------------------
        // 1. Userと紐付くContact情報を取得
        // ------------------------------------
        User u = [
            SELECT Id, LastName, FirstName, Email, ContactId, Contact.LastName, Contact.FirstName 
            FROM User 
            WHERE Id = :userId 
            LIMIT 1
        ];
    
        // 変更フラグ
        boolean userChanged = false;
        boolean contactChanged = false;
        
        // ------------------------------------
        // 2. User レコードの更新
        // ------------------------------------
        if (data.lastName != u.LastName) {
            u.LastName = data.lastName;
            userChanged = true;
        }
        if (data.firstName != u.FirstName) {
            u.FirstName = data.firstName;
            userChanged = true;
        }
        // ※メールアドレスも変更可能ですが、通常はユーザーがSalesforce側で変更しない限り同期しない方が安全です。
        // if (data.email != u.Email) {
        //     u.Email = data.email;
        //     userChanged = true;
        // }
    
        // ------------------------------------
        // 3. Contact レコードの更新
        // ------------------------------------
        if (u.ContactId != null) {
            Contact c = new Contact(Id = u.ContactId);
            
            // GoogleデータとContactデータを比較
            if (data.lastName != u.Contact.LastName) {
                c.LastName = data.lastName;
                contactChanged = true;
            }
            if (data.firstName != u.Contact.FirstName) {
                c.FirstName = data.firstName;
                contactChanged = true;
            }
            
            // Contactの更新実行
            if (contactChanged) {
                update c;
            }
        }

        // ------------------------------------
        // 4. User レコードの更新
        // 最終ログイン日時などの情報は自動で更新されるため、明示的に変更があったフィールドのみ更新を実行
        // ------------------------------------
        if (userChanged) {
            update u;
        }
    }
}
  • 特徴:
    • 設定値はすべて CMDT から取得(コード修正不要)。
    • メールアドレスで既存の Contact を検索し、重複作成を防止。
    • Contact の属性(VIPフラグ Is_VIP__c など)を見て、割り当てるプロファイルを動的に変更。
    • Customer Community Plus / Partner ライセンスにも対応。

上で示した Apex コードは以下のすべてのパターンを網羅しています。

  • Contactあり / Userなし: User を作成してログインさせます。
  • Contactなし / Userなし: Contact も User も新規作成します。
  • Contactあり / Userあり (Google未連携): User と Google を連携させます。
  • Contactあり / Userあり (Google連携済): Google の最新情報で User / Contact を更新します。

💡忘れずに!Plus / Partner ライセンスを使用する場合、紐付ける取引先(カスタムメタデータに設定した sForce や Apex 上の Google Auth Users)の所有者は、必ず ロール(Role)を持つ内部ユーザー に設定してください。

Step 5: サイトのログインページに追加

最後に、LWRサイトのログイン画面にボタンを表示させます。

  1. ワークスペース > [管理] > [ログインと登録]
  2. 「ログインページ設定」で [Google] にチェックを入れて保存。
  3. ビルダー > Login 画面
  4. 「ソーシャルログイン」コンポーネントを配置。
  5. 公開

これで完了です! Google アカウントでログインすると、Salesforce 側に自動的に「取引先責任者」と「ユーザ」が作成され、サイトにログインできるようになります。

  • ログインオプションを追加
  • ソーシャルログインコンポーネントを配置

Step6: 実際にログインしてみる

実際のログイン画面がこちら。

Google 認証を通過して、うまくユーザーが作成された後にログインできていることがわかります。

  • ログイン画面
  • Google認証
  • ログイン成功
  • ユーザー登録済み
  • アカウントを選択
  • アクセス許可

まとめ

ソーシャルログインは、UX向上のための強力な武器です。 しかし、単に繋ぐだけでなく、「既存顧客だったらどうする?」「VIP会員だったら?」 という業務要件を考慮した実装にしておくことで、運用の質が大きく変わります。

今回実装した「カスタムメタデータ × Apex」のパターンは、Google 以外のプロバイダ(LINEやFacebook)でも応用できる汎用的な設計です。ぜひ活用してください。

DXforceの管理人

福島 瑛二

2013年にJavaエンジニアとしてのキャリアをスタート。2019年にSalesforceと出会い、Salesforceエンジニアの道へ。

デザインや UI/UX の観点からもシステムを捉え、ユーザーにとって心地よい体験を実装することにやりがいを感じています。

CRM(顧客データ)や Data Cloud と連携した高度なサイトを目に見える形で表現できる Experience Cloud に大きな可能性を見出しており、バックエンドのデータ構造とフロントエンドの表現力を極めることがこれからの Salesforce エンジニアに求められるスキルだと確信しています。

Trailblazer: efukushima

福島 瑛二をフォローする

読者の声

タイトルとURLをコピーしました
DXforce 案内エージェント