1. やってみたこと
Salesforceのサーバ間インテグレーション用の OAuth 2.0 クライアントログイン情報フローでprivate_key_jwt
方式を利用できるか確認する
1.1. 背景など
OAuth 2.0 クライアント認証
こちらの記事を参考にすると、OAuth 2.0 クライアント認証には以下の方式があるようです
- client_secret_post
- 認可サーバが生成した
client_id
とclient_secret
をクライアントに提供する - クライアントはトークンリクエストする際に
client_id
とclient_secret
を含めて送信する
- 認可サーバが生成した
- client_secret_basic
- 認可サーバが生成した
client_id
とclient_secret
をクライアントに提供する - クライアントがトークンリクエストする際にBasic認証を使用して送信する
-
client_id:client_secret
をbase64エンコードしたものをAuthorization
ヘッダーに埋め込む
- 認可サーバが生成した
- client_secret_jwt
- 認可サーバが生成した
client_id
とclient_secret
をクライアントに提供する - クライアントがトークンリクエストする際にJWTを作成して、それを含めて送信する
- JWTを生成する際に
client_secret
を使用して署名する
- 認可サーバが生成した
- private_key_jwt
- 認可サーバが生成した
client_id
をクライアントに提供する - クライアントで秘密鍵と署名証明書を作成します
- クライアントで作成した署名証明書を認可サーバに提供する
- クライアントがトークンリクエストする際にJWTを作成して、それを含めて送信する
- JWTを生成する際に秘密鍵を使用して署名する
- 認可サーバが生成した
サーバ間インテグレーション用の OAuth 2.0 クライアントログイン情報フロー
こちらのSalesforceマニュアルでは、client_secret_post
とclient_secret_basic
の方法について記載があります
ただし、client_id
とclient_secret
へのアクセス権を持つすべてのユーザまたはアプリケーションはアクセストークンを取得することができます
Salesforceマニュアルでは、そのようなセキュリティリスクを考慮してコンシューマの秘密(client_secret
)を定期的に変更することを推奨しています
コンシューマの秘密の変更を行うと、そのタイミングでクライアントはアクセストークンを取得できない期間がどうしても発生してしまいます
それであればそもそもclient_secret
を使用しない方法の方が幾分かリスクは減り、運用コストも軽減するのではないかと思います
Salesforce側の接続アプリケーションのclient_id
と、クライアント側の秘密鍵に同時にアクセスすることは困難であると思いますので、JWTベアラーフローを検討したいところです
上記を踏まえて、クライアントログイン情報フローでprivate_key_jwt
方式が使用できるのかを見てみたいと思いました
2. バージョンなど
- Summer '23 Patch 10.8 (2023/7/3時点)
- sfdx-cli/7.206.6 darwin-x64 node-v18.15.0
% sfdx version
sfdx-cli/7.206.6 darwin-x64 node-v18.15.0
- スクラッチ組織で試してみる
- JWTの作成にJava, Mavenを使用します
- Java
% java --version
openjdk 17.0.7 2023-04-18
OpenJDK Runtime Environment Temurin-17.0.7+7 (build 17.0.7+7)
OpenJDK 64-Bit Server VM Temurin-17.0.7+7 (build 17.0.7+7, mixed mode, sharing)
- Maven
% mvn --version
Apache Maven 3.9.3 (21122926829f1ead511c958d89bd2f672198ae9f)
3. 試したこと
OAuth 2.0 クライアント認証
サーバ間インテグレーション用の OAuth 2.0 JWT ベアラーフロー
こちらを参照しながら試していきます
3.1. 確認する内容
OAuth 2.0 クライアント認証
こちらを参照すると、JWT要求セットの要素は以下のように記載されていました
- JWT要求セットの要素
- iss:
client_id
- sub:
client_id
- aud:
token endpoint
- iss:
サーバ間インテグレーション用の OAuth 2.0 JWT ベアラーフロー
SalesforceのJWTベアラーフローのマニュアルでは、JWT要求セットの要素は以下のように設定すると記載されていました
- JWT要求セットの要素
- iss:
client_id
(コンシューマ鍵) - sub: 接続するユーザー名
- aud: 認証サーバの URL
-
https://login.salesforce.com
またはhttps://test.salesforce.com
- 本番環境では前者、テスト環境(SandboxやScratch)では後者になります
-
- iss:
sub
に設定する値がキーに思います
sub
に設定する値について以下のケースを試してみます
Case 1) sub
が client_id
である場合
以下の組み合わせを試します
- sub
client_id
- aud
https://test.salesforce.com
推測としては、sub
がclient_id
だとどこに接続して良いのか分からない感じがします
Case 2) sub
が クライアントログイン情報フローで別のユーザとして実行に設定されているユーザのユーザー名である場合
SalesforceのJWTベアラーフローでは、sub
に接続ユーザのユーザー名を設定するので、このケースを試してみます
以下の組み合わせを試します
- sub
- クライアントログイン情報フローで別のユーザとして実行に設定されているユーザのユーザー名
- aud
https://test.salesforce.com
Case 3) sub
が クライアントログイン情報フローで別のユーザとして実行に設定されているユーザとは異なるユーザのユーザー名である場合
他のユーザー名を指定した場合はどうなるかを試してみます
以下の組み合わせを試します
- sub
- クライアントログイン情報フローで別のユーザとして実行に設定されているユーザとは異なるユーザのユーザー名
- aud
https://test.salesforce.com
3.2. クライアントログイン情報フロー用の接続アプリケーションの作成とアクセストークン取得の確認
まずは、クライアントログイン情報フローでアクセストークンが取得できる環境を準備します
Salesforceのサーバ間インテグレーション用の OAuth 2.0 クライアントログイン情報フローを試してみる
こちらを参照して環境を準備して正常にアクセストークンを取得できることを確認します
3.3. JWTベアラーフローのための準備
Salesforceのサーバ間インテグレーション用の OAuth 2.0 JWT ベアラーフローを試してみる
こちらを参照してJWTベアラーフローのための準備を行います
クライアントログイン情報フローでは、接続アプリケーションのOAuth ポリシー > 許可されているユーザ
はすべてのユーザは自己承認可能
で動作していたのでこの設定はこのままにしておきます
接続アプリケーションのプロファイルもここでは設定しないままにしておきます
JWTの作成やアクセストークンの取得方法についても上記記事の内容を利用します
3.4. sub
が client_id
である場合
JWTのJSON要求セットは以下のような形式です
{
"iss": client_id,
"sub": client_id,
"aud": "https://test.salesforce.com",
"exp": "1333685628"
}
アクセストークンを取得しようとすると以下のようなレスポンスが返ってきました
承認されていないという旨のメッセージです
{
"error": "invalid_grant",
"error_description": "user hasn't approved this consumer"
}
user hasn't approved this consumer
で検索すると以下のページがヒットしました
{ "error" : "invalid_grant", "error_description" : "user hasn't approved this consumer"}
Salesforce JWT User Hasn't Approved This Consumer (Again)
また、こちらの記事の内容も必要そうです
エラー「失敗: アクセスが承認されていません」
該当する接続アプリケーションの詳細 ([設定] | [アプリケーションを管理する] | [接続アプリケーション]) に移動すると、[OAuth ポリシー] セクションで [許可されているユーザー] が [管理者が承認したユーザーは事前承認済み] に設定されている可能性があります。
1.特定のユーザーグループにこのアプリへのアクセスを許可する手順は次のとおりです。
[プロファイルを管理する] とプロファイル関連リストを活用して、該当する Salesforce プロファイル (および関連するユーザー) へのアクセスを承認します。
接続アプリケーションに以下の設定を行います
-
OAuth ポリシー > 許可されているユーザ
を管理者が承認したユーザは事前承認済み
に設定 -
プロファイル
にクライアントログイン情報 > 別のユーザとして実行
に設定されているユーザーのプロファイル
もう1度アクセストークンを取得を試してみます
以下のようなレスポンスが返ってきました
{
"error": "invalid_grant",
"error_description": "user hasn't approved this consumer"
}
う〜ん、だめそうです。。。
3.5. Case 2) sub
が クライアントログイン情報フローで別のユーザとして実行に設定されているユーザのユーザー名である場合
JWTのJSON要求セットは以下のような形式です
{
"iss": client_id,
"sub": クライアントログイン情報フローで別のユーザとして実行に設定されているユーザのユーザー名,
"aud": "https://test.salesforce.com",
"exp": "1333685628"
}
3.4.で設定したOAuth ポリシー > 許可されているユーザ
やプロファイル
は設定したままにします
アクセストークンを取得しようとすると以下のようなレスポンスが返ってきました
{
"access_token": "******",
"scope": "api",
"instance_url": "https://MyDomain.my.salesforce.com",
"id": "https://test.salesforce.com/id/******/******",
"token_type": "Bearer"
}
正常にアクセストークンを取得することができました
3.6. Case 3) sub
が クライアントログイン情報フローで別のユーザとして実行に設定されているユーザとは異なるユーザのユーザー名である場合
続いて、他のユーザーでアクセストークンを取得しようとするとどうなるのか見てみます
アクセストークンを取得しようとすると以下のようなレスポンスが返ってきました
{
"access_token": "******",
"scope": "api",
"instance_url": "https://MyDomain.my.salesforce.com",
"id": "https://test.salesforce.com/id/******/******",
"token_type": "Bearer"
}
正常にアクセストークンを取得することができました
普通にJWTベアラーフローが動作したような感じです
3.7. Case 4) クライアントログイン情報フローでアクセストークンを取得してみる
接続アプリケーションには、クライアントログイン情報フローとJWTベアラーフローの両方の設定がある状態です
この状態でクライアントログイン情報フローでアクセストークンを取得しようとするとどうなるか見てみます
アクセストークンを取得しようとすると以下のようなレスポンスが返ってきました
{
"access_token": "******",
"signature": "******",
"scope": "api",
"instance_url": "https://MyDomain.my.salesforce.com",
"id": "https://test.salesforce.com/id/******/******",
"token_type": "Bearer",
"issued_at": "1688369423081"
}
正常にアクセストークンを取得することができました
3.8. 接続ユーザーのログイン履歴を見てみると
-
設定 > ユーザ > ユーザ
で該当ユーザを選択します -
ログイン履歴
をクリックします
ここを見てみると、ログインサブ種別
にOAuth クライアントログイン情報
となっているものとそうでないものがあります
つまり、クラアインログイン情報フローでリクエストするとログインサブ種別
にOAuth クライアントログイン情報
になり、JWTベアラーフローであればログインサブ種別
は空欄になるようです
クライアントログイン情報フローとJWTベアラーフローの併用ができることが分かりました
4. まとめると...
- クライアントログイン情報フロー
-
client_secret_post
、client_secret_basic
の方式を利用できる -
client_secret_jwt
の方式は未検証のため不明 -
private_key_jwt
の方式は利用不可
-
- 接続アプリケーション
- クライアントログイン情報フローとJWTベアラーフローの併用した設定ができる
- クライアントログイン情報フローでリクエストすると、
クライアントログイン情報 > 別のユーザとして実行
に設定したユーザーで実行される - JWTベアラーフローでは、他のユーザーも利用することができる
参考
OAuth 2.0 クライアント認証
サーバ間インテグレーション用の OAuth 2.0 クライアントログイン情報フロー
サーバ間インテグレーション用の OAuth 2.0 JWT ベアラーフロー
Salesforceのサーバ間インテグレーション用の OAuth 2.0 クライアントログイン情報フローを試してみる
Salesforceのサーバ間インテグレーション用の OAuth 2.0 JWT ベアラーフローを試してみる
{ "error" : "invalid_grant", "error_description" : "user hasn't approved this consumer"}
Salesforce JWT User Hasn't Approved This Consumer (Again)
エラー「失敗: アクセスが承認されていません」
おわり。