背景と応用シナリオ
現代のエンタープライズアーキテクチャでは、複数のクラウドサービスやオンプレミスアプリケーションが連携して動作することが当たり前になっています。この複雑な環境において、ユーザーIDの管理は大きな課題です。各システムが独自のユーザーデータベースを持つと、ユーザーは多数の認証情報を記憶・管理する必要があり、管理者にとってはセキュリティリスクと運用コストの増大を招きます。この課題を解決するのが SSO (シングルサインオン - Single Sign-On) です。
Salesforce 統合エンジニアとして、私たちは日々システム間の安全でシームレスなデータ連携を設計・実装しています。特に、外部アプリケーションが Salesforce のユーザー情報を利用してユーザーを認証するシナリオは非常に一般的です。例えば、以下のようなケースが考えられます。
応用シナリオ
- カスタム顧客ポータル: 企業が独自に開発した顧客向けウェブポータルで、顧客に Salesforce Community のログイン情報を使ってサインインさせる。これにより、顧客は新しいIDとパスワードを作成する必要がなくなり、利便性が向上します。
- モバイルアプリケーション: 営業担当者向けのカスタムモバイルアプリが、Salesforce の認証情報を用いてユーザーを認証し、そのユーザーの権限で Salesforce API にアクセスする。
- パートナー向けツール連携: パートナー企業が利用する外部SaaSツールに、Salesforce のパートナーユーザーとしてログインできるようにし、ID管理を一元化する。
これらのシナリオを実現するための技術的な基盤が、OAuth 2.0 と OpenID Connect です。OAuth 2.0 は「認可 (Authorization)」のためのフレームワークであり、「あるアプリケーションがユーザーの代わりに特定のリソース(例:Salesforce の取引先データ)にアクセスすることを許可する」仕組みを提供します。しかし、OAuth 2.0 単体では「ユーザーが誰であるか」を標準的な方法で証明する「認証 (Authentication)」の機能は限定的です。
そこで登場するのが OpenID Connect (OIDC) です。OIDC は OAuth 2.0 の上に構築されたシンプルなIDレイヤーであり、OAuth 2.0 の認可フローを利用して、ユーザーの認証と基本的なプロファイル情報を安全に連携先に提供します。統合エンジニアにとって、OIDC は Salesforce を中心としたセキュアなID連携エコシステムを構築するための強力なツールとなります。
原理の説明
OpenID Connect のフローを理解するためには、まず登場する主要な役割を把握することが重要です。
- End-User: サービスにログインしようとしている人間(ユーザー)。
- Relying Party (RP): ユーザーが利用したいアプリケーションやサービス。OAuth 2.0 のクライアントに相当します。日本語では「依拠当事者」と訳されます。
- OpenID Provider (OP): ユーザーを認証し、そのID情報を提供するエンティティ。今回の文脈では Salesforce がこの役割を担います。
OIDC の最も一般的で安全なフローは「認可コードフロー (Authorization Code Flow)」です。このフローは以下のようなステップで進行します。
- 認証リクエスト: End-User が RP(例:カスタムポータル)の「Salesforce でログイン」ボタンをクリックします。RP はユーザーのブラウザを OP(Salesforce)の認証エンドポイントへリダイレクトさせます。このとき、リクエストには
client_id
、redirect_uri
、scope
(openid
を必ず含める)、response_type=code
といったパラメータが含まれます。 - ユーザー認証と同意: End-User は Salesforce のログイン画面で認証情報を入力します。初回アクセスの場合、RP が要求する情報(例:メールアドレス、プロファイル情報)へのアクセスを許可するかどうかの同意画面が表示されます。
- 認可コードの発行: Salesforce はユーザーを認証し、同意が得られると、指定された
redirect_uri
にユーザーのブラウザをリダイレクトさせます。このリダイレクトURLには、一時的な「認可コード (Authorization Code)」が付与されます。 - トークンリクエスト: RP のバックエンドサーバーは、受け取った認可コードを使い、Salesforce のトークンエンドポイントにリクエストを送信します。この通信はサーバー間で行われるため、安全です。
- トークンの発行: Salesforce は認可コードを検証し、正当であれば RP に対して「ID Token」と「Access Token」を発行します。
- ID Token の検証とセッション確立: RP は受け取った ID Token を検証します。ID Token は JWT (JSON Web トークン - JSON Web Token) 形式の電子署名付きデータで、ユーザーのID情報(例:ユーザーID、名前、メールアドレス)が含まれています。RP はこの署名を検証し、内容が改ざんされていないこと、発行元が信頼できることなどを確認した上で、ユーザーのログインセッションを確立します。
このフローの中心となるのが ID Token です。これはユーザー認証の「証明書」として機能し、RP はこのトークンを信頼することでユーザーを安全に受け入れることができます。また、同時に発行される Access Token は、従来の OAuth 2.0 と同様に、保護されたリソース(例:Salesforce API)にアクセスするために使用されます。RP は Salesforce の UserInfo エンドポイント (UserInfo Endpoint) に Access Token を提示することで、より詳細なユーザー情報を取得することも可能です。
さらに、OIDC では ディスカバリーエンドポイント (Discovery Endpoint) と呼ばれる仕組みが提供されています。これは /.well-known/openid-configuration
という固定パスで公開されており、OP の各エンドポイントURL(認証、トークン、UserInfoなど)や、署名検証に必要な公開鍵の場所(JWKS URI)といった構成情報を機械可読な形式で提供します。RP はこの情報を利用することで、手動設定を最小限に抑え、動的に OP の構成を把握できます。
サンプルコード
OIDC フローにおける RP 側の実装、特に Salesforce から受け取った ID Token を検証するロジックは非常に重要です。ここでは、仮に RP のバックエンドが Apex で実装されていると仮定し、Salesforce の組み込みクラスである Auth.JWS
を使用して ID Token を検証するサンプルコードを示します。このロジックは、Java や Python など他の言語で実装する際にも基本的な考え方として応用できます。
このコードは、RP がトークンエンドポイントから ID Token を受け取った後の処理をシミュレートしています。
Apex での ID Token 検証
// Salesforce の Auth.JWS クラスを利用して OpenID Connect の ID Token を検証するサンプル // 本コードは Relying Party のサーバーサイドで実行されるロジックの概念を示すものです。 // トークンエンドポイントから受け取った ID Token (JWT形式の長い文字列) String idTokenString = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjIw...'; // Salesforce 接続アプリケーションのコンシューマ鍵 String connectedAppConsumerKey = '3MVG9...'; // 発行者 (Issuer) の URL。本番環境か Sandbox 環境かで異なります。 String expectedIssuer = 'https://login.salesforce.com'; // 認証リクエスト時に指定した nonce 値 String expectedNonce = 'a1b2c3d4e5f6'; try { // JWS (JSON Web Signature) オブジェクトを ID Token から作成 Auth.JWS jws = new Auth.JWS(idTokenString); // --- 署名検証 --- // Salesforce は RS256 アルゴリズムで署名します。検証には公開鍵が必要です。 // 公開鍵は、OIDC ディスカバリーエンドポイントから見つかる JWKS (JSON Web Key Set) URI から取得します。 // 実装では、この鍵をキャッシュし、定期的に更新する仕組みが必要です。 // ここでは、事前に取得した公開鍵文字列を仮定します。 // (注意: 実際の公開鍵は非常に長い文字列です) String salesforcePublicKey = '-----BEGIN PUBLIC KEY-----\nMIIBIjANBg...'; // 公開鍵とアルゴリズムを指定して署名を検証 Boolean isSignatureValid = jws.verify(salesforcePublicKey, 'RS256'); if (!isSignatureValid) { System.debug('致命的なエラー: ID Token の署名が無効です。'); // ここで処理を中断 return; } System.debug('ID Token の署名は有効です。'); // --- ペイロード (Claims) の検証 --- // ペイロードを JSON 文字列として取得し、Map に変換 String payloadJson = jws.getPayload(); Map<String, Object> claims = (Map<String, Object>) JSON.deserializeUntyped(payloadJson); // 1. Issuer (iss) の検証: 発行者が期待通り (Salesforce) であることを確認 if (claims.get('iss') != expectedIssuer) { System.debug('エラー: Issuer が一致しません。'); return; } // 2. Audience (aud) の検証: 受信者(自分自身)が接続アプリケーションのコンシューマ鍵と一致することを確認 if (claims.get('aud') != connectedAppConsumerKey) { System.debug('エラー: Audience が一致しません。'); return; } // 3. Expiration Time (exp) の検証: トークンが有効期限切れでないことを確認 // exp は UNIX エポック秒 (Integer) で格納されているため、キャストして比較 Integer expTime = (Integer) claims.get('exp'); Long nowInSeconds = Datetime.now().getTime() / 1000; if (expTime <= nowInSeconds) { System.debug('エラー: ID Token は有効期限切れです。'); return; } // 4. Nonce の検証: リプレイ攻撃を防ぐため、認証リクエスト時に送信した nonce と一致することを確認 if (claims.get('nonce') != expectedNonce) { System.debug('エラー: Nonce が一致しません。'); return; } // --- 全ての検証が成功 --- System.debug('全ての検証に成功しました。ユーザーは正常に認証されました。'); // クレームからユーザー情報を取得 String userId = (String) claims.get('sub'); // Salesforce の User ID String email = (String) claims.get('email'); String name = (String) claims.get('name'); System.debug('Salesforce User ID: ' + userId); System.debug('Email: ' + email); System.debug('Name: ' + name); // この後、RP 内でユーザーのセッションを作成するなどの処理に進みます。 } catch (Exception e) { System.debug('ID Token の処理中に例外が発生しました: ' + e.getMessage()); }
上記のコードは、Salesforce の公式ドキュメントで解説されている `Auth.JWS` クラスの機能に基づいています。統合エンジニアは、ID Token をただ受け取るだけでなく、このような厳格な検証プロセスを実装することが、システム全体のセキュリティを担保する上で不可欠であることを理解する必要があります。
注意事項
Salesforce を OIDC Provider として利用した統合を実装する際には、いくつかの重要な点に注意する必要があります。
接続アプリケーション (Connected App) の設定
全ての OIDC 連携は Salesforce の「接続アプリケーション」設定から始まります。特に以下の項目は正確に設定する必要があります。
- Enable OAuth Settings: これを有効にしないと始まりません。
- Callback URL: Salesforce が認証後にユーザーをリダイレクトさせる RP のエンドポイントです。誤った URL を指定するとフローが中断します。セキュリティのため、できるだけ具体的な URL を指定することが推奨されます。
- Selected OAuth Scopes: 必ず
openid
スコープを含める必要があります。これに加えて、UserInfo エンドポイントから取得したい情報に応じてemail
,profile
,address
などを追加します。API アクセスが必要な場合はapi
やfull
スコープも選択します。
セキュリティに関する考慮事項
- State パラメータ: 認証リクエスト時に `state` パラメータを必ず使用してください。これは、RP が生成したランダムな文字列であり、リダイレクト時にそのまま返却されます。RP はこの値がリクエスト時と一致することを確認することで、CSRF (クロスサイトリクエストフォージェリ - Cross-Site Request Forgery) 攻撃を防ぎます。
- Nonce パラメータ: `state` と同様に、`nonce` パラメータも認証リクエスト時に含める必要があります。この値は ID Token 内に `nonce` クレームとして返却されます。RP はこの値がリクエスト時と一致することを確認することで、リプレイ攻撃(一度使用された ID Token が不正に再利用される攻撃)を防止します。
- PKCE (Proof Key for Code Exchange): モバイルアプリや SPA (Single Page Application) のような、クライアントシークレットを安全に保持できない「パブリッククライアント」の場合、PKCE (Proof Key for Code Exchange) の利用が強く推奨されます。これは認可コードの横取り攻撃を防ぐための追加のセキュリティ機構です。
- ID Token の厳格な検証: サンプルコードで示した通り、ID Token の署名、発行者(iss)、受信者(aud)、有効期限(exp)、nonce の検証は必須です。いずれか一つでも欠けると、重大なセキュリティホールとなり得ます。
API 制限と鍵のローテーション
OIDC フロー自体が直接的に Salesforce の API ガバナ制限を大量に消費することはありません。しかし、発行された Access Token を使用して UserInfo エンドポイントや他の Salesforce API を呼び出す場合、それらは通常の API コールとしてカウントされます。また、Salesforce はセキュリティ上の理由から、ID Token の署名に使用する鍵を定期的にローテーション(更新)します。RP はハードコーディングされた公開鍵に依存せず、JWKS エンドポイントから動的に鍵を取得し、キャッシュするメカニズムを実装する必要があります。
まとめとベストプラクティス
OpenID Connect は、Salesforce を ID の中心 (IdP - Identity Provider) として活用し、外部アプリケーションとの間で安全かつ標準化されたユーザー認証を実現するための強力なフレームワークです。統合エンジニアとして OIDC の原理と実装上の注意点を深く理解することは、セキュアでスケーラブルなシステムアーキテクチャを設計する上で極めて重要です。
ベストプラクティス
- 適切なフローの選択: サーバーサイドの処理を持つウェブアプリケーションでは「認可コードフロー」を選択します。クライアントシークレットを安全に保持できないモバイルアプリや SPA では「PKCE 付き認可コードフロー」を使用します。セキュリティ上の脆弱性から、レガシーな「インプリシットフロー」は避けるべきです。
- 認証ライブラリの活用: OIDC の仕様は複雑な部分も多いため、ゼロから全てのロジックを実装するのではなく、各言語で提供されている認定済みの OIDC/OAuth 2.0 ライブラリを活用することを強く推奨します。これにより、トークン検証や鍵管理などの定型的な処理を安全かつ効率的に実装できます。
- ディスカバリーエンドポイントの利用: 可能な限り、Salesforce の OIDC ディスカバリーエンドポイント (
/.well-known/openid-configuration
) を利用して、各種エンドポイントURLや JWKS URI を動的に取得するようにクライアントを構成します。これにより、将来 Salesforce 側で URL が変更された場合にも、アプリケーションの改修なしで対応できます。 - 要求スコープの最小化: 「最小権限の原則」に従い、アプリケーションが必要とする最小限のスコープのみを要求してください。
openid
は必須ですが、それ以外のemail
やprofile
、api
といったスコープは、本当に必要な場合にのみ追加します。 - 堅牢なエラーハンドリング: ユーザーが認証を拒否した場合、トークンの検証に失敗した場合、Salesforce が利用できない場合など、あらゆるエラーシナリオを想定し、ユーザーに分かりやすいエラーメッセージを表示するなどの適切なハンドリングを実装してください。
Salesforce と OpenID Connect を正しく組み合わせることで、ユーザー体験を向上させながら、ID管理を一元化し、システム全体のセキュリティを強化することが可能です。
コメント
コメントを投稿