JWT BearerフローによるSalesforceセキュア連携:インテグレーションエンジニアのための実践ガイド

概要とビジネスシーン

JSON Web Token(JWT)は、情報をコンパクトでURLセーフな形で表現するためのオープン標準(RFC 7519)です。ステートレスな認証と情報交換を可能にし、特に分散システム間のセキュリティと効率を大幅に向上させます。Salesforce環境においてJWT Bearerフローを導入することで、外部システムとのセキュアなサーバー間連携を実現し、ユーザーの介入なしにAPIアクセスを確立できます。

実際のビジネスシーン

シーンA - 金融業界
あるオンラインバンキングプラットフォームでは、顧客の信用スコアや取引履歴をリアルタイムでSalesforceの顧客360ビューに連携し、融資判断やパーソナライズされたサービス提案に活用したいと考えていました。しかし、厳格なセキュリティ要件と大量のデータ流量が課題でした。
ビジネス課題:外部のバンキングシステムからSalesforceへの機密性の高い顧客データのセキュアなリアルタイム連携。
ソリューション:JWT Bearerフローを設定したSalesforce Connected Appを導入。バンキングシステムが顧客データを含むJWTを生成し、Salesforceのトークンエンドポイントに送信。Salesforceは署名を検証し、アクセストークンを発行。このトークンでSalesforce APIを呼び出し、リアルタイムでデータを更新。
定量的効果:認証プロセスが平均2秒から0.5秒に短縮され、API呼び出しのセキュリティ違反リスクが90%低減。顧客データの鮮度が向上し、融資審査時間の平均15%削減に貢献。

シーンB - ヘルスケア業界
複数の独立した医療情報システム(電子カルテ、予約システム、検査結果管理)を持つ病院グループが、Salesforce Health Cloudを導入して患者情報の統合管理を目指していました。各システム間の認証連携が複雑で、管理コストが高騰していました。
ビジネス課題:異なる医療システムとSalesforce Health Cloud間のセキュアでシームレスな患者情報連携とシングルサインオン(SSO)環境の構築。
ソリューション:JWT Bearerフローを活用し、各医療情報システムが患者固有の識別子を持つJWTを生成してSalesforceのトークンエンドポイントに送信。これにより、患者の同意に基づき、それぞれのシステムがSalesforce上の患者データにアクセスできるよう設定。また、外部システムからのSSO連携にも応用。
定量的効果:システム間の認証管理工数が月間200時間削減。HIPAA(Health Insurance Portability and Accountability Act)準拠のセキュリティを強化し、患者情報漏洩リスクを大幅に低減。医療従事者のデータアクセス時間が平均30%改善。

シーンC - 製造業
スマートファクトリーにおいて、多数のIoTデバイス(生産ラインセンサー、品質管理カメラ)が生成するリアルタイムデータをSalesforce Manufacturing Cloudに連携し、予知保全や生産効率の最適化を図りたいと考えていました。各デバイスに個別の資格情報を管理する複雑性が問題でした。
ビジネス課題:数千ものIoTデバイスからSalesforceへの膨大なセンサーデータのセキュアで効率的なストリーミング連携。
ソリューション:各IoTデバイスが、デバイスIDとタイムスタンプを含むJWTを生成し、SalesforceのConnected Appを通じて認証・認可を取得。取得したアクセストークンを用いてSalesforce Platform EventsやREST APIを介してデータを送信。これにより、デバイスレベルでの資格情報管理の負担を軽減。
定量的効果:IoTデバイスの認証設定時間が従来の80%削減。リアルタイムデータ連携により、機器故障の予知精度が15%向上し、予期せぬダウンタイムが月平均2日減少。

技術原理とアーキテクチャ

JWT Bearerフローは、デジタル署名されたトークンを介して認証を行うメカニズムです。SalesforceのConnected App(接続アプリケーション)を介して構成され、外部システム(クライアント)がJWTを生成し、Salesforceに提示することでアクセストークンを取得します。

基礎的な動作メカニズム

JWTは3つの部分から構成されます:

  • Header(ヘッダー):トークンのタイプ(JWT)と署名に使用されたアルゴリズム(例:HMAC SHA256, RSA SHA256)を定義します。
  • Payload(ペイロード):認証情報(クレーム)を含みます。これには、発行者(Issuer)、対象者(Audience)、サブジェクト(Subject)、有効期限(Expiration Time)などの標準クレーム、およびカスタムクレームが含まれます。
  • Signature(署名):ヘッダーとペイロードをBase64 URLエンコードし、秘密鍵(または公開/秘密鍵ペアの秘密鍵)を用いて暗号学的に署名したものです。これにより、トークンの改ざん防止と発行者の検証が可能になります。
JWT Bearerフローでは、外部システムが秘密鍵でJWTに署名し、SalesforceのConnected Appに登録された公開鍵でその署名を検証します。これにより、外部システムはパスワードやリフレッシュトークンを直接扱うことなく、セキュアな認証チャネルを確立できます。

主要コンポーネントと依存関係

  • Salesforce Connected App:JWT Bearerフローを有効にし、公開鍵証明書をアップロードする中心的な構成要素です。このアプリケーションが外部システムからのJWTを検証し、アクセストークンを発行します。
  • 公開鍵証明書(X.509):外部システムがJWTに署名するために使用する秘密鍵に対応する公開鍵をConnected Appにアップロードします。
  • 外部システム(JWT発行者):秘密鍵を安全に管理し、必要なクレームを含むJWTを生成する役割を担います。
  • OAuth 2.0 トークンエンドポイント:Salesforceが提供するAPIエンドポイントで、外部システムはJWTをPOSTしてアクセストークンを取得します。

データフロー

ステップ 説明 Salesforce側のアクション 外部システム側のアクション
1. 証明書ペアの生成 外部システムは秘密鍵と公開鍵のペアを生成し、公開鍵をX.509証明書としてSalesforceに登録。 Connected Appに公開鍵証明書をアップロード。 秘密鍵を安全に保管。
2. JWTの生成 外部システムはConnected AppのConsumer Key、Audience URL(Salesforceのトークンエンドポイント)、Subject(ユーザー名)、Expiration Timeなどを含むJWTを生成し、秘密鍵で署名。 - クレームを含むJWTを生成し、秘密鍵で署名。
3. アクセストークン要求 生成されたJWT(Assertion)を、grant_type=urn:ietf:params:oauth:grant-type:jwt-bearerとしてSalesforceのOAuth 2.0トークンエンドポイントにPOSTリクエストで送信。 - JWTを含むPOSTリクエストを送信。
4. JWTの検証 SalesforceはConnected Appに登録された公開鍵証明書を使用して、受信したJWTの署名を検証。Expiration TimeやIssuerなどのクレームも検証。 署名とクレームの検証。 -
5. アクセストークン発行 検証に成功した場合、Salesforceは外部システムにアクセストークンを発行。 アクセストークンを外部システムに返却。 -
6. Salesforce APIアクセス 外部システムは発行されたアクセストークンをBearerトークンとしてSalesforce APIリクエストのAuthorizationヘッダーに含めて送信。 APIリクエストの認証と認可。 アクセストークンを使用してSalesforce APIを呼び出し。

ソリューション比較と選定

ソリューション 適用シーン パフォーマンス Governor Limits 複雑度
JWT Bearer Flow サーバー間通信、ヘッドレス認証、SSO連携(一部)、パスワード交換不要なセキュア連携 初期設定は必要だが、実行時のオーバーヘッドは低い。ステートレスなためスケーラビリティが高い。 Salesforce API呼び出し制限(1日あたり250,000回)に準拠。JWT生成/検証自体はApex Governor Limits (CPUタイム) に影響。 中程度:Connected Appの設定、証明書管理、JWT生成ロジックが必要。
OAuth 2.0 Web Server Flow ユーザーインタラクションを伴う連携、公開クライアント(モバイルアプリなど)以外のWebアプリケーション ユーザー認証を伴うため、ステップが多い。 同様にSalesforce API呼び出し制限に準拠。 中程度:コールバックURL、認可コード、アクセストークン交換ロジックが必要。
Named Credential (OAuth 2.0) Salesforceから外部システムへのコールアウト、認証情報の一元管理 Salesforceプラットフォームが認証を自動処理するため、Apexからのコールアウト実装が簡素化されパフォーマンスは良好。 Apexコールアウト制限(トランザクションあたり100回、1日あたり250,000回)に準拠。 低:設定はSalesforce UIで完結し、Apexコードはエンドポイント指定のみで良い。

JWT Bearer Flow を使用すべき場合

  • ✅ 外部システムがユーザーインタラクションなしでSalesforce APIにアクセスする必要があるサーバー間連携。
  • パスワードやリフレッシュトークンの管理を避けたい場合、特にセキュリティが厳格な環境。
  • ステートレスな認証を実装し、スケーラビリティと耐障害性を高めたい場合。
  • 限られた期間のみ有効なアクセストークンを頻繁に生成し、セキュリティを強化したい場合。
  • ユーザーインタラクションが必要なログインフローや、デスクトップ/モバイルアプリケーションからの直接認証には不適用。

実装例

ここでは、Salesforceがクライアントとなり、Apexコード内でJWTを生成してSalesforce自身のOAuth 2.0トークンエンドポイントに認証リクエストを送信する例を示します。これは、Salesforceが外部システムとして振る舞い、自身のAPIにJWT Bearerフローでアクセスする基本的な方法を理解するのに役立ちます。

前提条件:

  1. Salesforce組織内にConnected Appを作成し、OAuthポリシーで「Selected OAuth Scopes」に必要なAPIアクセススコープ(例: api, refresh_token)を選択します。
  2. Connected AppにJWT Bearerフローで使用する証明書(X.509証明書)をアップロードし、その証明書に対応する秘密鍵を安全に保持します。Connected Appの「Consumer Key」も控えておきます。
  3. Connected Appのプロファイルまたは権限セットで、実行ユーザーがConnected Appにアクセスできるように許可します。
以下のコードは、上記のConnected Appと秘密鍵、Consumer Keyを使用し、SalesforceのOAuthトークンエンドポイントからアクセストークンを取得するApexクラスの例です。
public class JwtBearerFlowClient {

    // Salesforce Connected App の Consumer Key (発行者 'iss')
    // 本番環境では Custom Setting や Named Credential に保存することを推奨
    private static final String CONNECTED_APP_CONSUMER_KEY = '3MVG9WtYS_***.YOUR_CONSUMER_KEY.***';

    // 証明書に対応する秘密鍵 (Private Key)
    // 本番環境では Custom Setting や Named Credential に安全に保存することを強く推奨
    // OpenSSL で生成した秘密鍵の PEM 形式を「-----BEGIN PRIVATE KEY-----」から「-----END PRIVATE KEY-----」まで全て格納
    private static final String PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\n' +
                                                'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYk***YOUR_PRIVATE_KEY_CONTENT_HERE***\n' +
                                                '-----END PRIVATE KEY-----';

    // Salesforce の OAuth トークンエンドポイント (Audience 'aud' とリクエスト送信先)
    // 本番環境ではご自身のSalesforceインスタンスのURLに合わせる
    private static final String TOKEN_ENDPOINT = 'https://login.salesforce.com/services/oauth2/token';

    /**
     * JWT Bearerフローを使用してSalesforceからアクセストークンを取得するメソッド
     * @param subjectUserName 認証するSalesforceユーザーのユーザー名(SalesforceのUsernameフィールド)
     * @return 取得したアクセストークン
     */
    @AuraEnabled
    public static String getAccessTokenViaJwtBearerFlow(String subjectUserName) {
        // JWTヘッダーの作成: アルゴリズムとしてRS256を指定
        Auth.JWSHeader header = new Auth.JWSHeader();
        header.setAlgorithm(Auth.JWSHeader.Algorithm.RS256);

        // JWTペイロード(クレーム)の作成
        Auth.JWT jwt = new Auth.JWT(header);
        jwt.setIssuer(CONNECTED_APP_CONSUMER_KEY);      // 発行者 (iss): Connected App のコンシューマキー
        jwt.setAudience(TOKEN_ENDPOINT);               // 対象者 (aud): Salesforce のトークンエンドポイント
        jwt.setSubject(subjectUserName);                 // サブジェクト (sub): 認証するSalesforceユーザー名
        // 有効期限 (exp): 現在時刻から5分後 (ミリ秒単位) に設定
        jwt.setExpiration(System.currentTimeMillis() + 300000); // 5分 = 300,000ミリ秒

        // 秘密鍵を用いてJWTに署名
        Auth.JWS jws = new Auth.JWS(jwt, PRIVATE_KEY);
        String assertion = jws.compact(); // 署名済みのJWT文字列 (assertion) を取得

        System.debug('Generated JWT Assertion: ' + assertion);

        // Salesforce の OAuth トークンエンドポイントへの POST リクエストを構築
        HttpRequest req = new HttpRequest();
        req.setEndpoint(TOKEN_ENDPOINT);
        req.setMethod('POST');
        // Content-Type は application/x-www-form-urlencoded
        req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
        // 外部サービスへのコールアウトを許可する (Remote Site Settingも必要)
        req.setClientCertificateName('YOUR_CERTIFICATE_NAME_IN_SALESFORCE'); // 組織内の証明書名、必要に応じて

        // リクエストボディの構築
        // grant_type は JWT Bearer Flow の標準値
        String body = 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=' + EncodingUtil.urlEncode(assertion, 'UTF-8');
        req.setBody(body);

        // HTTPリクエストの送信
        Http http = new Http();
        HttpResponse res = http.send(req);

        // レスポンスの解析
        if (res.getStatusCode() == 200) {
            Map responseMap = (Map) JSON.deserializeUntyped(res.getBody());
            String accessToken = (String) responseMap.get('access_token');
            System.debug('Successfully obtained Access Token: ' + accessToken);
            return accessToken;
        } else {
            System.error('Error in JWT Bearer Flow. Status: ' + res.getStatusCode() + ', Status Message: ' + res.getStatus());
            System.error('Response Body: ' + res.getBody());
            throw new CalloutException('Failed to get access token via JWT Bearer Flow: ' + res.getBody());
        }
    }

    /*
    // このアクセストークンを使用してSalesforce REST APIを呼び出す例
    public static Map callSalesforceApi(String accessToken, String apiPath) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint(URL.getSalesforceBaseUrl().toExternalForm() + apiPath); // 例: '/services/data/vXX.0/sobjects/Account'
        req.setMethod('GET');
        req.setHeader('Authorization', 'Bearer ' + accessToken);
        req.setHeader('Content-Type', 'application/json');

        Http http = new Http();
        HttpResponse res = http.send(req);

        if (res.getStatusCode() == 200) {
            return (Map) JSON.deserializeUntyped(res.getBody());
        } else {
            System.error('Error calling Salesforce API. Status: ' + res.getStatusCode() + ', Body: ' + res.getBody());
            throw new CalloutException('Failed to call Salesforce API: ' + res.getBody());
        }
    }
    */
}

コードの解説:

  • CONNECTED_APP_CONSUMER_KEY: SalesforceのConnected Appに表示される「Consumer Key」を設定します。これがJWTのiss(発行者)クレームになります。
  • PRIVATE_KEY: Connected Appにアップロードした証明書に対応する秘密鍵を設定します。通常、PEM形式の文字列を格納します。本番環境ではCustom SettingやNamed Credential等に安全に保存すべきです。
  • TOKEN_ENDPOINT: SalesforceのOAuth 2.0トークンエンドポイントを指定します。これがJWTのaud(対象者)クレームになります。
  • Auth.JWSHeader: JWTのヘッダー部を作成し、署名アルゴリズムとしてRS256を指定します。
  • Auth.JWT: JWTのペイロード部(クレーム)を作成します。setIssuer, setAudience, setSubject, setExpiration で標準クレームを設定します。setSubjectには、Connected Appがアクセスを許可するSalesforceユーザーのユーザー名(User.Username)を設定します。
  • Auth.JWS: Auth.JWTオブジェクトとPRIVATE_KEYを用いてJWTにデジタル署名を行います。jws.compact()でURLセーフなJWT文字列を生成します。
  • HttpRequest: 生成したJWT(assertion)をgrant_type=urn:ietf:params:oauth:grant-type:jwt-bearerとして、SalesforceのトークンエンドポイントにPOSTリクエストで送信します。
  • レスポンスの解析: 成功した場合、JSON形式でアクセストークンを含むレスポンスが返されます。

注意事項とベストプラクティス

権限要件

  • Connected Appの設定
    • OAuthポリシーで「Relax IP restrictions」を有効にするか、またはアクセス元のIP範囲を制限する必要があります。
    • 「Permitted Users」を「Admin approved users are pre-authorized」に設定し、実行ユーザーのプロファイル/権限セットにConnected Appへのアクセスを割り当てます。
    • 必要なOAuthスコープ(例: api, full, webなど)を適切に選択します。
  • Remote Site Settings:Apexから外部エンドポイント(この例ではhttps://login.salesforce.comまたは自身のドメイン)へのコールアウトを行う場合、対応するURLを「リモートサイトの設定」に追加する必要があります。

Governor Limits

JWT Bearerフローに関連するGovernor Limitsは以下の通りです。

  • Apexコールアウト制限
    • 同期トランザクションあたり最大100回のコールアウト。
    • 非同期トランザクション(Batch Apex, Queueable Apexなど)で1日あたり最大250,000回のコールアウト。(Developer Editionは50,000回)
  • CPUタイム制限
    • 同期トランザクションで最大10秒。
    • 非同期トランザクションで最大60秒。
    JWTの生成や特に検証(複雑なペイロードやアルゴリズムの場合)はCPUタイムを消費するため、大量のJWTをApexで処理する場合は注意が必要です。
  • ヒープサイズ制限
    • 同期トランザクションで最大6MB。
    • 非同期トランザクションで最大12MB。
    JWTの文字列が非常に長くなる場合や、大量のJWTをメモリ上で扱う場合はこの制限に抵触する可能性があります。

エラー処理

  • 不正な署名(Invalid Signature):秘密鍵と公開鍵が一致しない、またはJWTが改ざんされた場合に発生します。Connected Appの証明書と外部システムの秘密鍵が正しくペアになっているか確認してください。
  • 期限切れ(Expired Token):JWTのexp(有効期限)クレームが過去の日付の場合に発生します。外部システムで生成するJWTの有効期限を適切に設定し、同期問題がないか確認してください。
  • 不正な発行者(Invalid Issuer):JWTのissクレームがConnected AppのConsumer Keyと一致しない場合に発生します。
  • 不正な対象者(Invalid Audience):JWTのaudクレームがSalesforceのトークンエンドポイントURLと一致しない場合に発生します。
  • CalloutException:ネットワークエラーやSalesforceからの予期せぬHTTPレスポンスの場合に発生します。try-catchブロックで適切に処理し、デバッグログやHTTPレスポンスボディを調査してください。

パフォーマンス最適化

  1. JWTの有効期限を短く設定:セキュリティ強化のため、JWTの有効期限(expクレーム)は数分〜数十分と短く設定し、アクセストークンが切れたら新しいJWTを生成して再認証するフローを確立します。
  2. 必要なクレームのみ含める:ペイロードに含める情報を最小限にすることで、JWTのサイズを小さく保ち、ネットワーク転送量と解析時間を削減します。
  3. Connected Appのキャッシュ活用:SalesforceはConnected Appの設定と公開鍵証明書をキャッシュするため、一度設定が完了すればその後の検証は効率的に行われます。外部システムの負荷が高い場合は、Salesforce側のConnected Appの設定がボトルネックにならないよう適切にスケールアウトする構成を検討します。
  4. Named Credentialの利用:Salesforceから外部システムへのコールアウトにJWT Bearerフローを使用する場合、Named Credentialの認証プロトコルとしてOAuth 2.0 JWT Bearer Flowを設定することで、Apexコードから認証ロジックを排除し、Salesforceが自動でJWT生成・認証を処理するため、開発と実行の効率が向上します。

よくある質問 FAQ

Q1:JWT Bearerフローでリフレッシュトークンは利用できますか?

A1:いいえ、JWT Bearerフローはステートレスな認証メカニズムであり、リフレッシュトークンは利用できません。アクセストークンの有効期限が切れたら、外部システムは新しいJWTを生成し、再度トークンエンドポイントに送信して新しいアクセストークンを取得する必要があります。

Q2:JWTのデバッグ方法は?

A2:JWTのデバッグには、jwt.io のようなオンラインツールが非常に役立ちます。生成されたJWT文字列を貼り付けることで、ヘッダー、ペイロード、署名を視覚的に確認できます。Salesforce側では、デバッグログ(ApexコードのSystem.debug出力やHTTPコールアウトの詳細ログ)、およびConnected Appのイベントログ(設定監査履歴)を確認して、認証失敗の原因を特定します。

Q3:JWT認証のパフォーマンス監視にはどのような指標を追跡すべきですか?

A3:主要な監視指標としては、アクセストークン取得の成功率、認証にかかる平均応答時間(レイテンシー)、エラー率(特に不正なJWT関連のエラー)、そして外部システムからのAPIコールアウト数とSalesforce APIの利用状況(API Limit Usage)が挙げられます。これらの指標を監視し、異常を早期に検知することで、安定した統合運用を維持できます。

まとめと参考資料

JWT Bearerフローは、Salesforceと外部システム間のセキュアで効率的なサーバー間連携を実現するための強力なツールです。ステートレスな性質によりスケーラビリティが高く、パスワード管理の複雑さを軽減します。インテグレーションエンジニアとしては、Connected Appの適切な設定、証明書の安全な管理、そしてApexでのJWT生成・利用のベストプラクティスを理解することが成功の鍵となります。今回紹介した実装例と注意事項を参考に、堅牢なSalesforceインテグレーションを構築してください。

公式リソース

コメント