Salesforce OpenID Connect: 安全なID連携のための包括的ガイド

背景と適用シナリオ

現代のエンタープライズアーキテクチャにおいて、ID管理と認証はセキュリティの基盤です。ユーザーは複数のアプリケーションやサービスを日常的に利用しており、それぞれに異なる認証情報を管理することは非効率的かつセキュリティリスクを高めます。この課題を解決するため、Single Sign-On (SSO) が広く採用されています。SSOを実現するための標準プロトコルとして、SAMLやOpenID Connectが主流となっています。

OpenID Connect (OIDC) は、OAuth 2.0 プロトコルを拡張した、シンプルでモダンなID認証レイヤーです。OAuth 2.0がリソースへのアクセス許可(認可 - Authorization)に特化しているのに対し、OIDCはそれに加えて「ユーザーが誰であるか」を証明する認証(Authentication)の機能を提供します。具体的には、ID Token と呼ばれるJSON Web Token (JWT) 形式のID情報を導入することで、これを実現しています。

SalesforceプラットフォームにおけるOIDCの主な適用シナリオは以下の通りです。

1. SalesforceをIDプロバイダー (Identity Provider - IdP) として利用する

外部のWebアプリケーションやモバイルアプリケーションが、Salesforceのユーザー認証情報を利用してログインできるようになります。例えば、Heroku上で動作するカスタムアプリケーションや、外部のパートナーポータルなどが、Salesforceユーザーにシームレスなログイン体験を提供できます。Salesforceの「接続アプリケーション (Connected App)」機能を用いて、OIDC準拠のIdPを容易に構築できます。

2. Salesforceをリライングパーティ (Relying Party - RP) として利用する

ユーザーがGoogle, Microsoft Azure AD, Oktaなどの外部IDプロバイダーのアカウント情報を使ってSalesforceにログインできるようになります。これにより、従業員は普段利用している会社の認証情報でSalesforceにアクセスでき、利便性が向上します。Salesforceの「認証プロバイダー (Auth. Provider)」機能を利用して設定します。

3. セキュアなAPI連携

Salesforce内外のAPIを連携させる際、OIDCを利用してユーザーのID情報を安全に受け渡し、コンテキストに基づいたAPIアクセス制御を実現します。例えば、外部システムがSalesforce APIをコールする際に、単なるAPIキーではなく、特定のユーザーに紐づいたアクセストークンとID情報を要求することで、よりきめ細やかなセキュリティを確保できます。


原理説明

OIDCの動作原理を理解するためには、まず主要な登場人物と概念を把握する必要があります。

  • Identity Provider (IdP - IDプロバイダー): ユーザーの認証情報を管理し、認証を行い、Relying Partyに対してユーザーのID情報を提供するエンティティです。 (例: Salesforce, Google, Azure AD)
  • Relying Party (RP - 依拠当事者): IdPにユーザー認証を委託し、IdPから提供されたID情報を受け取って利用するアプリケーションやサービスです。(例: SalesforceにログインしようとするカスタムWebアプリ)
  • User-Agent: ユーザーが操作するブラウザなどのクライアントソフトウェアです。
  • ID Token: 認証されたユーザーに関する情報(クレーム - claims)を含むJWT形式のトークンです。ユーザーの一意な識別子(sub)、発行者(iss)、対象者(aud)などが含まれており、RPはこれを検証することでユーザーを認証します。
  • Access Token: OAuth 2.0で定義されているトークンで、特定のリソース(APIなど)へのアクセス権限を保持します。OIDCでは、UserInfoエンドポイントへのアクセスなどに利用されます。
  • UserInfo Endpoint: IdPが提供する保護されたリソースエンドポイントで、Access Tokenを提示することで、ID Tokenには含まれていない追加のユーザー情報を取得できます。

OIDCで最も一般的に利用されるフローは認可コードフロー (Authorization Code Flow) です。以下にその流れを解説します。

1. 認証リクエスト (Authentication Request)
ユーザーがRP(例: カスタムアプリ)にアクセスすると、RPはユーザーをIdP(例: Salesforce)の認可エンドポイントにリダイレクトします。このとき、`response_type=code`、`client_id`、`scope`(`openid` は必須)、`redirect_uri`、`state`(CSRF対策)などのパラメータを付与します。

2. ユーザー認証と同意 (User Authentication and Consent)
ユーザーはIdPのログイン画面で認証情報を入力します。認証後、IdPはユーザーに対し、RPが要求している情報(プロファイル情報など)へのアクセスを許可するかどうかの同意を求めます。

3. 認可コードの発行 (Authorization Code Issuance)
ユーザーが同意すると、IdPはUser-Agentを介して、RPが指定した`redirect_uri`にリダイレクトさせます。このとき、URLのクエリパラメータとして一時的な認可コード (Authorization Code) と、リクエスト時に指定した`state`パラメータを返却します。

4. トークンリクエスト (Token Request)
RPは受け取った認可コードを使い、バックチャネル(サーバー間通信)でIdPのトークンエンドポイントにリクエストを送信します。このリクエストには、認可コード、`client_id`、`client_secret`などが含まれます。

5. トークンの発行 (Token Issuance)
IdPは認可コードとクライアント情報を検証し、問題がなければID TokenAccess Token、そして任意でRefresh TokenをRPに返却します。

6. ID Tokenの検証とセッション確立 (ID Token Validation and Session Establishment)
RPは受け取ったID Tokenの署名、発行者(iss)、対象者(aud)、有効期限(exp)などを検証します。検証に成功すれば、ユーザーの認証が完了したとみなし、RP内でのセッションを確立します。RPはID Tokenに含まれるユーザー識別子(`sub`クレーム)を使って、自システムのユーザーと紐付けます。

7. (任意) UserInfoエンドポイントへのアクセス
RPは、必要に応じてAccess Tokenを使い、IdPのUserInfoエンドポイントにアクセスして、追加のユーザー情報を取得できます。


示例代码

ここでは、SalesforceをRelying Party (RP)として設定し、外部IdP(例: Google)で認証したユーザーの情報を、Apexを使ってUserInfoエンドポイントから取得するシナリオを考えます。この実装は、外部IdPで認証したユーザー情報を使ってSalesforce内でカスタムロジックを実行する場合に非常に役立ちます。

ステップ1: Salesforceで認証プロバイダーを設定する

まず、Salesforceの「設定」から「認証プロバイダー」を新規作成します。プロバイダー種別として「Open ID Connect」を選択し、外部IdPから提供された以下の情報を入力します。

  • クライアントID (Client ID)
  • クライアントシークレット (Client Secret)
  • 承認エンドポイントURL (Authorize Endpoint URL)
  • トークンエンドポイントURL (Token Endpoint URL)
  • UserInfoエンドポイントURL (UserInfo Endpoint URL)
  • デフォルトの範囲 (Default Scopes): `openid email profile` などを指定します。

保存後、Salesforceは「コールバックURL」を生成します。このURLを外部IdPのアプリケーション設定に登録する必要があります。

ステップ2: ApexでUserInfo情報を取得する

ユーザーが上記で設定した認証プロバイダー経由でSalesforceにログインすると、Salesforceはアクセストークンを安全に保管します。Apexの `Auth.AuthToken` クラスを利用することで、このトークンを取得し、UserInfoエンドポイントへのコールアウトを実行できます。

以下のApexクラスは、指定された認証プロバイダー名とユーザーIDを使ってアクセストークンを取得し、UserInfoエンドポイントを呼び出してユーザー情報を取得する例です。このコードは、Salesforce Developerドキュメントの `Auth.AuthToken` クラスの利用方法に基づいています。

public class UserInfoService {

    /**
     * @description 指定された認証プロバイダーのUserInfoエンドポイントからユーザー情報を取得する
     * @param authProviderName 認証プロバイダーの開発者名 (例: 'Google')
     * @param userId SalesforceのユーザーID
     * @return UserInfoエンドポイントからのレスポンスボディ(JSON文字列)
     */
    public static String getUserInfo(String authProviderName, Id userId) {
        
        // 認証プロバイダーの情報を取得
        AuthProvider provider = [SELECT Id FROM AuthProvider WHERE DeveloperName = :authProviderName LIMIT 1];
        if (provider == null) {
            throw new UserInfoException('指定された認証プロバイダーが見つかりません: ' + authProviderName);
        }

        // サードパーティアカウントリンクからプロバイダーのユーザーIDを取得
        // このリンクは、ユーザーが初めてOIDCフローを完了したときに自動的に作成される
        ThirdPartyAccountLink tpal = [
            SELECT RemoteIdentifier 
            FROM ThirdPartyAccountLink 
            WHERE AuthProviderId = :provider.Id AND UserId = :userId 
            LIMIT 1
        ];
        if (tpal == null) {
            throw new UserInfoException('指定されたユーザーのサードパーティアカウントリンクが見つかりません。');
        }

        String providerUserId = tpal.RemoteIdentifier;

        // Auth.AuthTokenクラスを使用して、保存されているアクセストークンを取得
        // このメソッドは、Salesforceが安全に管理しているトークンにアクセスするための公式な方法
        String accessToken = Auth.AuthToken.getAccessToken(provider.Id, providerUserId);

        if (String.isBlank(accessToken)) {
            throw new UserInfoException('アクセストークンの取得に失敗しました。トークンが無効か、期限切れの可能性があります。');
        }

        // UserInfoエンドポイントのURLを取得
        String userInfoUrl = Auth.AuthConfiguration.getAuthProviderInfo(authProviderName).getUserInfoUrl();

        // HTTPリクエストを作成してUserInfoエンドポイントを呼び出す
        HttpRequest req = new HttpRequest();
        req.setEndpoint(userInfoUrl);
        req.setMethod('GET');
        // AuthorizationヘッダーにBearer Tokenとしてアクセストークンを設定
        req.setHeader('Authorization', 'Bearer ' + accessToken);
        
        Http http = new Http();
        HttpResponse res;
        String responseBody = '';

        try {
            res = http.send(req);
            responseBody = res.getBody();

            // 成功応答(200 OK)以外の場合は例外をスロー
            if (res.getStatusCode() != 200) {
                throw new UserInfoException('UserInfoエンドポイントへのコールアウトに失敗しました。 Status: ' + res.getStatus() + ', Body: ' + responseBody);
            }

        } catch (System.CalloutException e) {
            System.debug(LoggingLevel.ERROR, 'UserInfoコールアウトでエラーが発生しました: ' + e.getMessage());
            throw new UserInfoException('UserInfoエンドポイントへの通信中にエラーが発生しました。 ' + e.getMessage());
        }

        return responseBody;
    }

    // カスタム例外クラス
    public class UserInfoException extends Exception {}
}

このコードを実行するには、`UserInfoService.getUserInfo('Google', UserInfo.getUserId());` のように呼び出します。これにより、現在ログインしているユーザーのGoogleアカウント情報を取得できます。


注意事項

権限 (Permissions)

  • 認証プロバイダーの管理: 「アプリケーションのカスタマイズ」権限を持つ管理者が認証プロバイダーを設定する必要があります。
  • Apexの実行: 上記のApexコードを実行するユーザーは、そのクラスへのアクセス権が必要です。
  • コールアウト: Apexから外部システムへのコールアウトを行うため、対象のUserInfoエンドポイントURLを「リモートサイトの設定 (Remote Site Settings)」に登録する必要があります。ただし、Named Credential(指定ログイン情報)を利用することで、この設定を不要にし、よりセキュアに認証情報を管理できます。

API制限 (API Limits)

  • Apexガバナ制限: Apexからのコールアウトは、Salesforceのガバナ制限に従います。1トランザクションあたりのコールアウト回数(同期Apexでは100回)や、タイムアウト(最大120秒)に注意が必要です。
  • IdP側のレート制限: 外部IdPは、トークンエンドポイントやUserInfoエンドポイントへのアクセスに対してレート制限を設けている場合があります。大量のユーザーが一斉にログインするようなシナリオでは、IdP側の制限も考慮する必要があります。

エラー処理 (Error Handling)

OIDCフローやAPIコールアウトは、ネットワークの問題やトークンの有効期限切れなど、様々な理由で失敗する可能性があります。サンプルコードで示したように、`try-catch`ブロックによる堅牢なエラーハンドリングは不可欠です。特に、アクセストークンが期限切れになっている場合は、ユーザーに再認証を促すなどのフォールバック処理を実装する必要があります。

セキュリティ (Security)

  • stateパラメータ: 認可リクエスト時に`state`パラメータを生成し、コールバック時に検証することで、Cross-Site Request Forgery (CSRF) 攻撃を防ぎます。SalesforceがRPとして動作する場合、この処理は自動的に行われます。
  • nonceパラメータ: ID Tokenのリプレイ攻撃を防ぐために利用されます。これもSalesforceがRPとして動作する際に内部的に処理されます。
  • ID Tokenの検証: RPは、受け取ったID Tokenの署名、発行者(`iss`)、対象者(`aud`)、有効期限(`exp`)を必ず検証しなければなりません。Salesforceの認証プロバイダー機能はこれを自動的に行いますが、カスタム実装を行う場合はこの検証がセキュリティの要となります。
  • PKCE (Proof Key for Code Exchange): 認可コードの横取り攻撃を防ぐための拡張仕様で、特にモバイルアプリやSPA (Single Page Application) のような、クライアントシークレットを安全に保持できないクライアントで推奨されます。

まとめとベストプラクティス

OpenID Connectは、OAuth 2.0の認可フレームワーク上に構築された、柔軟でセキュアな認証プロトコルです。Salesforceプラットフォームでは、「接続アプリケーション」と「認証プロバイダー」という2つの主要な機能を通じて、IdPとしてもRPとしてもOIDCを容易に活用できます。これにより、ユーザーにシームレスなSSO体験を提供し、システム間のAPI連携を安全に行うことが可能になります。

SalesforceでOIDCを実装する際のベストプラクティスは以下の通りです。

  1. 適切なフローの選択: サーバーサイドで処理を行うWebアプリケーションでは「認可コードフロー」を、モバイルアプリやSPAではセキュリティを強化する「PKCE付き認可コードフロー」を使用します。
  2. 最小権限の原則: `scope`パラメータで要求する情報は、アプリケーションが必要とする最小限の範囲(例: `openid`, `profile`, `email`)に留めます。
  3. Named Credentialの活用: ApexからUserInfoエンドポイントなどの保護されたリソースにアクセスする際は、ハードコードされたURLや認証情報を避け、「指定ログイン情報 (Named Credential)」を利用します。これにより、コードの変更なしにエンドポイント情報を管理でき、認証情報のセキュリティも向上します。
  4. セキュアなクライアントシークレット管理: IdPから発行された`client_secret`は機密情報です。バージョン管理システムに含めたり、コードにハードコードしたりせず、安全な場所に保管します。
  5. ログと監視: 認証に関するイベント(ログイン成功、失敗、トークン発行など)を適切にログに記録し、不正なアクティビティがないか監視する体制を整えます。

これらの原理とベストプラクティスを理解し、Salesforceの標準機能を最大限に活用することで、堅牢でスケーラブルなID連携ソリューションを構築することができるでしょう。

コメント