外部Webサイトからコンバージョン率を落とさずにSalesforceへ会員登録させたい場面において、Headless Identityによるパスワードレス登録は有効な選択肢です。
第二回目である本記事では、自己登録時のデータ不整合を防ぐためのバックエンド処理(Apex)と、コミュニティユーザー特有の権限管理について解説します。
前回の記事はこちら
【ペットクリニックサイト 制作日誌 Vol.1】ReactとSalesforce Headless Identityで作るパスワードレス会員ポータルアーキテクチャ
今回の連載記事で作成したReactサイトとSalesforceコンポーネントをGitHubで公開しています。 ぜひ Clone して、あなたの組織で動かしてみてください。

事実(仕様に基づく登録ハンドラーの前提)
Headless Identityのセルフ登録フローでは、Auth.HeadlessSelfRegistrationHandler インターフェースを実装したApexクラスが必要です。このインターフェースの createUser メソッドは、戻り値として User オブジェクトを返却する仕様です。

実装コード(複数オブジェクトのアトミック生成)
フロントエンドから送信されたJSONデータを受け取り、個人取引先とカスタムオブジェクトを生成する実装です。
global class HeadlessRegistrationHandler implements Auth.HeadlessSelfRegistrationHandler {
global User createUser(Id accountId, Id profileId, Map<String, String> registrationAttributes, String password) {
// registrationAttributes内のcustomUserDataMapはJSON文字列として格納されているため、再度デシリアライズが必要です
String customDataStr = registrationAttributes.get('customUserDataMap');
Map<String, Object> customData = (Map<String, Object>) JSON.deserializeUntyped(customDataStr);
// 処理中のエラー時にすべてをロールバックするため、Savepointを設定します
Savepoint sp = Database.setSavepoint();
try {
// 個人取引先の作成
Account a = new Account(
LastName = registrationAttributes.get('lastName'),
FirstName = registrationAttributes.get('firstName'),
RecordTypeId = [SELECT Id FROM RecordType WHERE SobjectType='Account' AND DeveloperName='PersonAccount' LIMIT 1].Id
);
insert a;
// 個人取引先作成後、Userに紐付けるためにPersonContactIdを再取得します
Account insertedAcc = [SELECT PersonContactId FROM Account WHERE Id = :a.Id];
// フロントエンドから渡された拡張パラメータを用いてカスタムオブジェクトを作成します
Pet__c pet = new Pet__c(
Name = (String)customData.get('petName'),
Species__c = (String)customData.get('petSpecies'),
Owner__c = insertedAcc.PersonContactId
);
insert pet;
// Userオブジェクトをインスタンス化します。ここで insert u; を実行するとエラーになるため設定のみ行います
User u = new User();
u.ProfileId = profileId;
u.ContactId = insertedAcc.PersonContactId;
u.Username = registrationAttributes.get('email');
u.Email = registrationAttributes.get('email');
u.Alias = 'guest';
u.TimeZoneSidKey = 'Asia/Tokyo';
u.LocaleSidKey = 'ja_JP';
u.EmailEncodingKey = 'UTF-8';
u.LanguageLocaleKey = 'ja';
// プラットフォーム側にDMLを委譲するため、インスタンスを返却します
return u;
} catch (Exception e) {
Database.rollback(sp);
throw new Auth.DiscoveryCustomErrorException('Registration failed: ' + e.getMessage());
}
}
}
推測・見解(設計判断とトレードオフ)
個人取引先(Person Account)の採用とパフォーマンス
B2Cモデルにおいては、AccountとContactを個別に作成するよりも個人取引先モデルが自然です。
コード内で insert a; の直後に [SELECT PersonContactId FROM Account WHERE Id = :a.Id] を発行していますが、Id(プライマリキー)指定のSOQLはインデックスが完全に効くため処理時間は数ミリ秒で完了します。フロントエンド側のレイテンシに悪影響を及ぼすことはありません。
個人取引先(Person Account)を作成した直後にUserオブジェクトへ紐付けるためには、Account自身のIdではなく裏側で作られた取引先責任者(Contact)のIdが必要です。そのため、insert 直後に必ずSOQLで取得し直す一手間が発生します。
return User を避ける
通常のApex開発の癖で insert User; と書いてしまうと MIXED_DML_OPERATION エラー(非同期オブジェクトと通常オブジェクトの同一トランザクションでのDML混在)で即エラーとなります。Headless Identityのフレームワークにインスタンスごと投げ渡し、最後の保存はSalesforceに任せるのが絶対のルールです。
トランザクション管理の重要性
最後のUserのinsertはプラットフォーム側で行われます。もしカスタムオブジェクト作成中にエラーが起きた場合、Savepoint を使ってロールバックしておかないと「ユーザーは作られなかったのに、飼い主のいないペットのデータだけがDBに残る」というゴミデータ問題を誘発します。
開発時の知見(ハマりどころ)
- MIXED_DML_OPERATION エラー
Userオブジェクトに対して明示的にinsert u;を実行すると、非同期処理とDMLの衝突によるエラーが発生します。メソッドの戻り値としてインスタンスを返す仕様に則る必要があります。 - customUserDataMap のパース漏れ
フロントエンドから送るJSONのcustomdataはregistrationAttributes内で文字列として格納されます。再度JSON.deserializeUntypedを実行しないと値が取得できません。 - 初期化時のNull起因エラー
登録時にユーザー入力がない項目(予約日時など)がnullになるケースを想定せず、後続のマイページ表示用クラスで比較処理を行うと、ログイン後に500エラーが発生します。データの初期化と参照時のNullチェックはセットで実装してください。 - Without Sharing とオブジェクト権限
Apexクラスをwithout sharingで定義しても、 Experience Cloud上でコミュニティユーザーが実行する際、オブジェクト自体へのアクセス権限 (CRUD / OLS) は評価されます。プロファイルや権限セットにカスタムオブジェクト(Pet__c等)の参照権限がないとsObject type 'Pet__c' is not supportedエラーとなるため、権限設定とApex側でのtry-catchによるエラーハンドリングが必須です。
まとめ
Headless Identityにおける自己登録処理は、プラットフォーム側の仕様に合わせた正確なハンドラー実装が求められます。
Userオブジェクトは明示的にinsertせず戻り値として返却する点や、JSONデータの確実なデシリアライズなど、細かな仕様への理解が不可欠です。また、without sharingを指定したクラスであっても、Experience Cloud環境下ではコミュニティユーザーのオブジェクト権限(CRUD/OLS)が厳格に評価されるため、適切なプロファイル・権限セットの割り当てと、トランザクション全体を保護するロールバック制御をセットで実装するのがポイントです。
続いて第三弾の記事もご覧ください。
参考URL
Configure Experience Cloud Settings for Headless Registration




読者の声