こんにちは、システム開発チームです。
Salesforce のログインフローを使用したユーザーあたりの同時セッション数を制限について調べた際、公式ドキュメントの方法では足りない点についてわかったことを記します。
実装の手順的には概ねドキュメントの通りではあるのですが、未管理パッケージとしてインストールされる Apex クラスがユーザーセッションを識別する条件に問題がありました。
if(s.ParentId == null && s.SessionType != 'TempUIFrontdoor')
Salesforceでは、1つのブラウザログイン(親)に対して、複数の子セッション(Visualforce、Lightning、Content など)が自動的に生成されるため、 親セッションのみをカウント しています。TempUIFrontdoor は、Salesforceの内部処理や、特定の遷移(設定画面への移動や、外部サイトからのシングルサインオン直後など)で一時的に生成される「一時的なセッション」です。
この条件では、接続アプリケーションに対して OAuth 認証を行なっている Oauth2 セッションもカウントされるため、正確でありません。
公式ドキュメントには上記条件に加えて、InternalServiceCall が付け加えられています。
// Count only parent and non-temp and non-internal sessions
if(s.ParentId == null && s.SessionType != 'TempUIFrontdoor' && s.SessionType != 'InternalServiceCall' )
これはシステム内部で Lightning Experience や Apex のサービスが別のサービスを呼び出す際にバックグラウンドで生成されるセッションですが、これも Oauth2 セッションなどがカウントされるため正確ではありません。
色々調べた結果、Salesforce の標準画面(Classic または Lightning)へのログインによって作成される、最も一般的なセッション種別の UI のみをカウントするのが正解でした。
if(s.ParentId == null && s.SessionType == 'UI' && s.IsCurrent == false)
加えて、現在の自分以外のセッションを識別したい場合は IsCurrent を使用します。こちらは API バージョン 37.0 以降で使用できます。 パッケージをインストールした場合はこれより古い API バージョンのため、追加する場合は手動で API バージョンを上げる必要があります。
これで概ねログインセッションの識別は可能ですが、厳密にはさらにもう一手間必要でした。
UI セッションはパスワードを認証した時点で生成されます。ところが、多要素認証を行なっている場合、確認コードが認証されていなくてもセッションが生成されます。確認コードの認証段階で中断されるとログインが成功していない余計な UI セッションが残るため、有効なセッションの識別に支障があります。これはユーザーセッション情報 AuthSession だけでは判別できません。
結論としては、LoginHistoryId からのリレーションで LoginHistory.LoginHistory.Status が Success のセッションだけをフィルターする必要があります。
しかし、ここでまた問題が…
SOQL の where 句に追加すると
field 'Status' can not be filtered in a query call
とエラーになってしまいます。
LoginHistory オブジェクトリファレンス によると、どうやら絞り込みができない項目のようです。
すべての項目を絞り込めるわけではありません。絞り込みは、次の項目でのみ実行できます。
・AuthenticationServiceId
・CipherSuite
・CountryIso
・Id
・LoginTime
・LoginType
・LoginUrl
・NetworkId
・OptionsIsGet
・OptionsIsPost
・TlsProtocol
・UserId
オブジェクトによってはそんな仕様もあるんですね…。
LoginHistory.Status については、SOQL で一旦抽出してからロジックでフィルターすることができました。
// 判定用のセッションリスト取得(LoginHistoryをリレーションシップで結合)
List<AuthSession> sessions = [
SELECT Id, ParentId, SessionType, IsCurrent, LoginHistoryId, LoginHistory.Status
FROM AuthSession
WHERE UsersId = :userid];
for (AuthSession s : sessions)
{
// LoginHistory.Statusが'Success'でない場合はスキップ
if (s.LoginHistory != null && s.LoginHistory.Status != 'Success') {
continue;
}
// 親かつUIかつ自分以外
if(s.ParentId ==null&& s.SessionType =='UI'&& s.IsCurrent ==false) {
これでようやくログインセッションのチェックが行えるようになります。
同時セッションパッケージの利用で期待通りの結果にならなかった方への参考になれば幸いです。