背景と応用シナリオ
Salesforce 開発者として、私たちは日々の業務で外部システムと Salesforce を連携させる、いわゆるサーバー間インテグレーションの要件に頻繁に直面します。例えば、夜間バッチで基幹システムから Salesforce のデータを更新したり、CI/CD パイプラインが自動的にメタデータをデプロイしたりするケースです。これらのシナリオで大きな課題となるのが「認証」です。
ユーザーが画面を介してログイン情報を入力する通常の Web サーバーフローやユーザーエージェントフローは、このような自動化されたプロセスには適していません。パスワードを直接設定ファイルに保存するのはセキュリティ上の重大なリスクを伴います。そこで登場するのが OAuth 2.0 JWT Bearer Flow です。このフローは、ユーザーの介入なしに、システムが安全に Salesforce API へのアクセス権(アクセストークン)を取得するための標準的な方法として設計されています。
このフローを利用することで、外部アプリケーションは事前に Salesforce と共有したデジタル証明書を用いて自身を認証し、特定のユーザーとして API を実行する権限を得ることができます。これにより、パスワードをネットワーク上でやり取りすることなく、セキュアでスケーラブルなインテグレーションを実現することが可能になります。
原理説明
JWT Bearer Flow の中核をなすのが、その名の通り JWT (JSON Web Token - JSONウェブトークン) です。JWT は、認証情報を安全にやり取りするためのコンパクトで自己完結したオープンスタンダード (RFC 7519) です。JWT は以下の3つのパートで構成されており、それぞれがドット (`.`) で区切られています。
1. ヘッダー (Header)
ヘッダーは通常、トークンのタイプ (JWT) と、署名に使用されるアルゴリズム(例: RS256)の2つの部分で構成されます。RS256 は、RSA 署名アルゴリズムと SHA-256 ハッシュアルゴリズムを組み合わせたもので、公開鍵暗号方式を利用します。
2. ペイロード (Payload)
ペイロードには、クレーム (Claims) と呼ばれる情報が含まれます。これは、エンティティ(通常はユーザー)と追加のメタデータに関する記述です。Salesforce の JWT Bearer Flow では、以下のクレームが必須となります。
- iss (Issuer): 発行者。Salesforce の接続アプリケーションのコンシューマキー (Consumer Key) を指定します。
- sub (Subject): 対象者。このアプリケーションが代理で操作を行うユーザーの Salesforce ユーザー名を指定します。
- aud (Audience): 受信者。トークンの発行先を示します。本番環境では `https://login.salesforce.com`、Sandbox 環境では `https://test.salesforce.com` を指定します。
- exp (Expiration Time): 有効期限。トークンが無効になる時刻を Unix タイムスタンプ(1970年1月1日からの秒数)で指定します。セキュリティのため、通常は数分程度の短い期間を設定します。
3. 署名 (Signature)
署名は、JWT の完全性と認証性を保証するための最も重要な部分です。これは、エンコードされたヘッダー、エンコードされたペイロード、秘密鍵 (Private Key) を、ヘッダーで指定されたアルゴリズム(RS256)で署名することによって作成されます。Salesforce 側は、事前に接続アプリケーションに登録された公開鍵 (Public Key) を使ってこの署名を検証します。署名が正しければ、Salesforce はペイロードの情報が改ざんされておらず、信頼できる発行元からのものであることを確認できます。
サンプルコード
Salesforce 開発者として、Salesforce 内部から外部の JWT 認証を要求する API を呼び出すシナリオも考えられます。ここでは、Apex を使って JWT を生成し、HTTP リクエストに含める方法を見ていきましょう。このコードは、Apex の標準クラスである `Auth.JWT` および `Auth.JWS` を使用しており、Salesforce 公式のベストプラクティスに基づいています。
この例では、Salesforce に保存されている自己署名証明書を使用して JWT に署名し、外部サービスへのコールアウトを行います。
// Auth.JWT クラスのインスタンスを作成します。 Auth.JWT jwt = new Auth.JWT(); // iss (Issuer) クレームを設定します。 // これは通常、外部サービス側で払い出されたクライアントIDやAPIキーになります。 jwt.setIss('YOUR_ISSUER_OR_CLIENT_ID'); // sub (Subject) クレームを設定します。 // 誰の代理で処理を行うかを示すユーザーIDやメールアドレスなどを指定します。 jwt.setSub('user@example.com'); // aud (Audience) クレームを設定します。 // 呼び出し先のエンドポイントやサービスを識別するURLを指定します。 jwt.setAud('https://external-service.com/api'); // addClaim メソッドで追加のカスタムクレームを設定することも可能です。 // 例えば、特定の権限範囲 (scope) を要求する場合など。 jwt.addClaim('scope', 'read write'); // Salesforce の「証明書と鍵の管理」で作成した証明書の一意の名前を指定します。 // この証明書に対応する秘密鍵が署名に使用されます。 String certificateName = 'My_SelfSigned_Cert'; // Auth.JWS (JSON Web Signature) のインスタンスを作成します。 // コンストラクタに JWT オブジェクトを渡します。 Auth.JWS jws = new Auth.JWS(jwt, certificateName); // issue() メソッドを呼び出して、署名済みの JWT を文字列として取得します。 // この時点で、ヘッダー、ペイロード、署名が結合された完全な JWT が生成されます。 String token = jws.getCompactSerialization(); // --- 生成した JWT を使用して HTTP コールアウトを実行 --- // HTTPリクエストを作成 HttpRequest req = new HttpRequest(); // 呼び出し先の API エンドポイントを設定 req.setEndpoint('callout:My_Named_Credential/some_path'); req.setMethod('POST'); // 'Authorization' ヘッダーに 'Bearer' スキームで JWT を設定します。 // これが JWT Bearer 認証の標準的な形式です。 req.setHeader('Authorization', 'Bearer ' + token); req.setHeader('Content-Type', 'application/json'); // リクエストボディを設定 (必要に応じて) req.setBody('{"key":"value"}'); // HTTP クライアントでリクエストを送信 Http http = new Http(); HTTPResponse res = http.send(req); // レスポンスを処理 if (res.getStatusCode() == 200) { System.debug('Success: ' + res.getBody()); } else { System.debug('Error: ' + res.getStatusCode() + ' ' + res.getStatus() + ' ' + res.getBody()); }
注意事項
JWT Bearer Flow を実装・運用する際には、いくつかの重要な点に注意する必要があります。
1. 接続アプリケーションの設定
Salesforce をリソースサーバー(APIを提供する側)として利用する場合、[設定] > [アプリケーションマネージャ] から接続アプリケーション (Connected App) を正しく設定する必要があります。「OAuth 設定の有効化」をオンにし、「デジタル署名を使用」にチェックを入れ、クライアント(外部システム)が持つ公開鍵をアップロードします。この公開鍵が、送信されてくる JWT の署名検証に使われます。
2. ユーザーの事前承認
JWT の `sub` クレームで指定されたユーザーは、その接続アプリケーションの使用を事前に許可している必要があります。管理者はプロファイルまたは権限セットを通じて、「管理者が承認したユーザは事前承認済み」ポリシーを設定し、対象のユーザーにそのプロファイル/権限セットを割り当てることで、ユーザーの操作なしに承認プロセスを完了させることができます。これを怠ると `invalid_grant: user has not approved this consumer` というエラーが発生します。
3. 証明書の管理
署名に使用する秘密鍵 (Private Key) は、外部システム側で厳重に管理する必要があります。決してハードコーディングしたり、バージョン管理システムにコミットしたりしてはいけません。Key Vault などのセキュアなストレージサービスを利用することが推奨されます。また、証明書には有効期限があるため、証明書のローテーション計画を立て、期限切れによるサービス停止を防ぐ必要があります。
4. クロックのずれ (Clock Skew)
JWT の `exp` クレームは時刻に依存するため、JWT を生成するサーバーと Salesforce サーバーとの間に大きな時刻のずれがあると、トークンが有効期限切れ、または未来のトークンとして拒否される可能性があります。NTP (Network Time Protocol) などを使用して、サーバーの時刻を正確に同期させることが重要です。
5. エラーハンドリング
Salesforce のトークンエンドポイント (`/services/oauth2/token`) は、認証失敗時に詳細なエラーコードを返します。例えば `invalid_grant` は署名の検証失敗、ユーザーの未承認、期限切れなど複数の原因が考えられます。`invalid_client_id` は `iss` クレームのコンシューマキーが間違っていることを示します。これらのエラーを適切にログに記録し、原因を特定できるような仕組みを構築することが、安定した運用には不可欠です。
まとめとベストプラクティス
OAuth 2.0 JWT Bearer Flow は、Salesforce とのサーバー間インテグレーションにおいて、セキュリティと自動化を両立させるためのデファクトスタンダードです。私たち開発者は、その仕組みを正しく理解し、安全に実装する責任があります。
以下に、実装におけるベストプラクティスをまとめます。
- 最小権限の原則 (Principle of Least Privilege): インテグレーション専用のユーザーを作成し、そのユーザーには連携に必要な最小限のオブジェクト権限と項目レベルセキュリティのみを付与したプロファイル・権限セットを割り当ててください。
- 秘密鍵の安全な保管: 秘密鍵はアプリケーションの最も重要な資産です。AWS Key Management Service, Azure Key Vault, HashiCorp Vault などの専用サービスを利用して管理し、アプリケーションからは実行時にのみアクセスできるように設計してください。
- 短い有効期限: JWT の `exp` クレームで設定する有効期限は、可能な限り短く(例: 5分以内)設定してください。これにより、万が一トークンが漏洩した場合でも、不正利用される時間を最小限に抑えることができます。
- 監査とモニタリング: Salesforce の「ログイン履歴」や「イベントモニタリング」を活用して、API ログインの成功・失敗を定期的に監視してください。不審なアクティビティを検知するためのアラートを設定することも重要です。
これらのプラクティスを遵守することで、私たちは堅牢でセキュアなインテグレーションを構築し、ビジネスの価値を最大化することができます。
コメント
コメントを投稿