背景と応用シナリオ
Salesforce インテグレーションエンジニアとして、私たちは日々、Salesforceと外部システム間のセキュアで効率的なデータ連携という課題に直面しています。特に、ユーザーの介在なしにシステム間で直接通信を行うサーバー間インテグレーションは、多くのビジネスプロセスで不可欠です。例えば、夜間のバッチ処理による基幹システムからのデータ同期、外部のイベントをトリガーとしたSalesforce上のレコード更新、あるいはCI/CDパイプラインからのメタデータデプロイメントなどが挙げられます。
このようなシナリオでは、従来のユーザー名とパスワードを用いた認証フローは適切ではありません。なぜなら、認証情報を安全に保管・管理することが困難であり、セキュリティリスクが非常に高くなるためです。また、ユーザーのパスワード変更や多要素認証(MFA)の導入によって、インテグレーションが突然停止してしまう可能性もあります。
ここで強力なソリューションとなるのが、OAuth 2.0 JWT (JSON Web Token - JSON Webトークン) Bearer Flow です。このフローは、ユーザーの認証情報を直接扱うことなく、デジタル署名されたトークンを用いてサーバー間の信頼関係を確立し、API (Application Programming Interface - アプリケーション・プログラミング・インターフェース) へのアクセスを許可する仕組みです。これにより、インテグレーションのセキュリティと安定性を大幅に向上させることができます。本記事では、Salesforceインテグレーションエンジニアの視点から、JWT Bearer Flowの原理を解き明かし、具体的な実装方法とベストプラクティスを解説していきます。
原理説明
JWT Bearer Flowの核心は、信頼できる第三者(この場合はインテグレーションクライアント)が発行し、秘密鍵で署名したJWTをSalesforceに提示することで、アクセストークンを取得する点にあります。Salesforceは、事前に登録された公開鍵(証明書)を用いてその署名を検証し、正当なリクエストであると判断した場合にのみ、APIアクセスを許可するアクセストークンを発行します。
このフローは、以下の主要なコンポーネントとステップで構成されます。
1. JWTの構造
JWTは、3つの部分から構成される、ドット(.)で区切られたBase64Urlエンコード文字列です。
- Header (ヘッダー): トークンのタイプ("JWT")と、署名に使用されるアルゴリズム(例: "RS256")が含まれます。
- Payload (ペイロード): Claim (クレーム) と呼ばれる、エンティティ(通常はユーザー)に関する情報や追加の属性を保持します。SalesforceのJWT Bearer Flowでは、以下のクレームが必須または重要です。
- iss (Issuer - 発行者): JWTの発行者。SalesforceのConnected App (接続アプリケーション) のコンシューマキー(Client ID)を指定します。
- sub (Subject - 主体): 承認を求めるユーザーのユーザー名。このユーザーとしてAPIが実行されます。
- aud (Audience - 対象者): トークンの対象者。本番環境では
https://login.salesforce.com
、Sandbox環境ではhttps://test.salesforce.com
を指定します。 - exp (Expiration Time - 有効期限): JWTの有効期限。Unixエポックタイムスタンプで指定します。通常は数分以内という短い期間を設定します。
- Signature (署名): ヘッダーとペイロードをエンコードした文字列を、指定されたアルゴリズムと Private Key (秘密鍵) を用いて署名したものです。この署名により、JWTが改ざんされていないこと、そして信頼できる発行者によって作成されたことを保証します。
2. 認証フロー
- 事前準備:
- クライアント側で、Private Key (秘密鍵) とそれに対応する Public Key (公開鍵) を含むデジタル Certificate (証明書) を生成します。
- Salesforce側でConnected App (接続アプリケーション) を作成し、「API (Enable OAuth Settings)」を有効にします。そして、「Use digital signatures」をチェックし、クライアント側で生成した証明書(公開鍵)をアップロードします。
- Connected Appのプロファイルまたは権限セットを、インテグレーションで利用するユーザーに割り当て、事前承認を済ませておきます。
- JWTの生成と署名:
- クライアントアプリケーション(インテグレーションサーバー)は、必要なクレーム(iss, sub, aud, exp)を含むJWTを構築します。
- 保持している秘密鍵を使用して、JWTにデジタル署名を付与します。
- アクセストークンの要求:
- クライアントは、SalesforceのOAuth 2.0トークンEndpoint (エンドポイント) (
/services/oauth2/token
) に対してPOSTリクエストを送信します。このリクエストのボディには、grant_type
としてurn:ietf:params:oauth:grant-type:jwt-bearer
を、assertion
として生成したJWTを含めます。
- クライアントは、SalesforceのOAuth 2.0トークンEndpoint (エンドポイント) (
- Salesforceによる検証と応答:
- Salesforceはリクエストを受け取ると、JWTの
iss
クレームからコンシューマキーを特定し、対応するConnected Appを検索します。 - Connected Appに登録されている証明書(公開鍵)を使ってJWTの署名を検証します。
- 署名が有効であり、かつクレーム(aud, expなど)も正当であることを確認します。
- 検証が成功すると、Salesforceは Access Token (アクセストークン) とインスタンスURLを含むJSONレスポンスを返します。
- Salesforceはリクエストを受け取ると、JWTの
- APIアクセス:
- クライアントは、取得したアクセストークンをHTTPヘッダー(
Authorization: Bearer [アクセストークン]
)に含めて、Salesforce APIへのリクエストを実行します。
- クライアントは、取得したアクセストークンをHTTPヘッダー(
この一連の流れにより、クライアントはパスワードを一切扱うことなく、セキュアにSalesforce APIへのアクセス権を得ることができるのです。
示例代码
ここでは、Apexを使用してSalesforce自身から別のSalesforce組織へJWT Bearer Flowで認証する例を示します。このコードは、サーバー間連携の一環として、あるSalesforce組織がトリガーとなって別の組織のAPIを呼び出すシナリオで利用できます。このコードは、Salesforce Developerの公式ドキュメントにあるJWT Bearer Flowの実装に基づいています。
まず、認証の詳細を格納するためのNamed Credential(指定ログイン情報)を設定することを強く推奨しますが、ここではフローの原理を明確にするため、直接コード内で各パラメータを指定しています。
public class JWTBearerFlowExample { // 別の組織で作成した接続アプリケーションのコンシューマキー private static final String CONSUMER_KEY = 'YOUR_CONNECTED_APP_CONSUMER_KEY'; // 認証を要求する先の組織のログインURL private static final String AUTH_URL = 'https://login.salesforce.com/services/oauth2/token'; // Sandboxの場合は 'https://test.salesforce.com/...' // 認証するユーザーのユーザー名 private static final String USERNAME = 'user@example.com'; // 証明書ストアにある証明書の一意の名前 private static final String CERT_UNIQUE_NAME = 'SelfSignedCertForJWT'; public static String getAccessToken() { // 1. JWTオブジェクトを初期化 Auth.JWT jwt = new Auth.JWT(); // 2. クレームを設定 // iss (Issuer): 発行者。接続アプリケーションのコンシューマキーを指定します。 jwt.setIss(CONSUMER_KEY); // sub (Subject): 認証するユーザーのユーザー名を指定します。 jwt.setSub(USERNAME); // aud (Audience): 対象者。ログインURLを指定します。 jwt.setAud(AUTH_URL); // 3. 追加のクレームを設定可能(オプション) // jwt.setAdditionalClaims(new Map<String, Object>{'claim_name' => 'claim_value'}); // 4. JWS (JSON Web Signature) を生成 // Auth.JWSコンストラクタにJWTと証明書名を渡します。 // Salesforceは証明書ストアから秘密鍵を取得し、JWTに署名します。 Auth.JWS jws = new Auth.JWS(jwt, CERT_UNIQUE_NAME); // 5. トークンエンドポイントに送信する認証情報を生成 // grant_typeとassertion(署名済みJWT)を指定します。 String tokenRequest = 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=' + jws.getCompactSerialization(); // 6. HTTPリクエストを作成してアクセストークンを要求 HttpRequest request = new HttpRequest(); request.setEndpoint(AUTH_URL); request.setMethod('POST'); request.setHeader('Content-Type', 'application/x-www-form-urlencoded'); request.setBody(tokenRequest); Http http = new Http(); HttpResponse response = http.send(request); String accessToken = null; // 7. レスポンスを処理 if (response.getStatusCode() == 200) { Map<String, Object> responseBody = (Map<String, Object>)JSON.deserializeUntyped(response.getBody()); accessToken = (String)responseBody.get('access_token'); System.debug('Access Token successfully retrieved: ' + accessToken); } else { System.debug('Error response: ' + response.getBody()); // エラーハンドリングを実装 throw new CalloutException('Failed to get access token. Status: ' + response.getStatus() + ', Body: ' + response.getBody()); } return accessToken; } }
注意事項
権限と事前承認
JWT Bearer Flowを成功させるには、適切な権限設定が不可欠です。
- ユーザー権限:
sub
クレームで指定されたユーザーは、「API 有効」権限を持っている必要があります。 - Connected Appのアクセス許可: Connected Appの設定で、「許可されているユーザー」ポリシーを「管理者が承認したユーザーは事前承認済み」に設定することを強く推奨します。これにより、個々のユーザーが手動でアプリを承認するポップアップ画面が不要になります。設定後、関連するプロファイルまたは権限セットをConnected Appに割り当て、インテグレーションユーザーがそのプロファイル/権限セットに属していることを確認してください。
API制限
JWT Bearer Flowを利用したAPIコールも、通常のSalesforce APIガバナ制限の対象となります。大量のデータを同期する場合や高頻度でAPIを呼び出す場合は、組織のAPIコールリミットを消費することを念頭に置き、効率的なAPI利用(例えば、Bulk APIの活用など)を計画する必要があります。
エラーハンドリング
インテグレーションを堅牢にするためには、適切なエラーハンドリングが欠かせません。JWT Bearer Flowでよく発生するエラーとその原因を理解しておくことが重要です。
invalid_grant
: 最も一般的なエラーの一つです。原因は多岐にわたります。- ユーザーがConnected Appを承認していない(事前承認設定が不十分)。
- ユーザーが無効になっている、または凍結されている。
- 証明書が一致しない、または失効している。
sub
で指定したユーザー名が間違っている。
invalid_client_id
:iss
クレームで指定したコンシューマキーが正しくありません。invalid_request
: リクエスト形式が不正です。例えば、必須パラメータが欠けている場合などです。expired_assertion
: JWTの有効期限(exp
クレーム)が切れています。クライアントとサーバー間の時刻のずれ(クロックスキュー)が原因である可能性もあります。JWTの有効期間を数分に設定し、両サーバーのNTP設定が同期していることを確認してください。
これらのエラーが発生した場合は、Connected Appの監査履歴や、デバッグログを有効にして詳細な原因を調査することが有効です。
セキュリティ
JWT Bearer Flowのセキュリティは、秘密鍵の管理に大きく依存します。秘密鍵が漏洩すると、第三者が正規のクライアントになりすましてSalesforceにアクセスできてしまいます。
- 秘密鍵の厳重な保管: 秘密鍵は、アクセスが厳しく制限された安全な場所に保管してください。Apexから利用する場合は、Salesforceの証明書と鍵の管理機能で証明書を作成またはインポートし、コードからはその一意の名前を参照するようにします。これにより、秘密鍵がコード内にハードコーディングされるのを防ぎます。
- キーローテーション: 定期的に鍵ペアを更新するキーローテーションのポリシーを策定・実施してください。これにより、万が一鍵が漏洩した場合の影響を最小限に抑えることができます。
まとめとベストプラクティス
OAuth 2.0 JWT Bearer Flowは、サーバー間インテグレーションにおける認証の課題を解決するための、非常にセキュアで信頼性の高い標準的な方法です。ユーザーの認証情報を直接扱うことなく、デジタル署名に基づいた信頼関係を構築することで、安全かつ自動化されたAPIアクセスを実現します。
インテグレーションエンジニアとして、このフローを実装する際のベストプラクティスは以下の通りです。
- Named Credentials(指定ログイン情報)の活用: Apexから外部システムにコールアウトする場合、エンドポイントURLや認証パラメータをコードにハードコーディングするのではなく、Named Credentialsを使用してください。JWT Bearer Flowの場合、「ID 種別」を「指定ユーザ」、「認証プロトコル」を「OAuth 2.0」として設定し、トークンエンドポイントやコンシューマキー、証明書などを宣言的に管理できます。これにより、コードの可搬性が高まり、環境間(Sandboxから本番など)の移行も容易になります。
- 専用のインテグレーションユーザーの作成: インテグレーション専用のAPIユーザーを作成し、最小権限の原則に従って、必要なオブジェクトや項目へのアクセス権のみを付与したプロファイル/権限セットを割り当てます。これにより、インテグレーションに起因する操作の追跡が容易になり、セキュリティリスクを低減できます。
- 堅牢なロギングと監視: API連携の成功・失敗を記録するカスタムオブジェクトやプラットフォームイベントを作成し、エラーが発生した際に管理者に通知する仕組みを構築します。これにより、インテグレーションの問題を迅速に検知し、対応することが可能になります。
- 秘密鍵の安全な管理と定期的なローテーション: 前述の通り、秘密鍵の管理は最重要事項です。Salesforceプラットフォーム内で完結する場合は証明書ストアを利用し、外部システムの場合は、Azure Key VaultやAWS KMSなどの専用サービスを利用して鍵を安全に保管・管理することを検討してください。
これらのベストプラクティスを遵守することで、Salesforce JWT Bearer Flowのメリットを最大限に引き出し、セキュアで、スケーラブルかつメンテナンス性の高いインテグレーションを構築することができるでしょう。
コメント
コメントを投稿