OAuth2 JWT Bearer Token フロー
SalesforceのAPIを使ってSalesforce内のデータにアクセスする場合、最近はOAuth2を使って認証するのが一般的かと思います。
しかしながら、OAuth2で最もポピュラーなフローであるAuthorization CodeフローはWebブラウザを使ったリダイレクトの仕組みを用いており、一旦ユーザエージェントを介すことが前提となることから、システム間連携などで接続するには少し不向きともいえます。また、複数のSalesforceユーザアカウントで接続する必要がある場合などは、接続するユーザアカウントごとに取得したトークンをセキュアに管理する必要があることも懸念点の1つです。
ここで、Salesforceがサポートしているもう1つのフローである JWT Bearer Tokenフローについて紹介します。これは、事前に信頼関係を構築できるシステムからSalesforceへAPI接続する場合に有効な方法です。
※ なお、このフローの元となっている OAuth2 JWT Profileの仕様についてはRFC7523としてまとまっています。
概要
JWT Bearer Tokenフローの前に、まずJWTについて簡単に説明します。
JWTとは、JSON形式のメッセージをWeb上で扱いやすい文字形式エンコードし、電子署名を施せるようにしたものです(正確には署名したものはJWSといいますが、署名まで含めてJWTと呼称する場合は多いです)。電子署名が施されていますので、メッセージに記載されている内容が正しく送られているかどうかは簡単に検証できるようになっています。
JWTのメッセージに含まれる内容としては、一般的に以下の内容が含まれています。
- 発行者(Issuer): 誰がこのメッセージを作成したか
- 利用者(Audience): 誰に対してこのメッセージが作成されたか。
- 主体(Subject): 誰の情報についてこのメッセージに記載されているか
JWT Bearer Tokenフローでは、アクセストークンを取得する際に、このJWT形式のメッセージを添付してSalesforceにリクエストを行います。
Salesforceは、リクエストで受け取ったJWTに対して、自分宛てに作成されたメッセージかどうか(Audience)、どのアプリケーションサービスからリクエストされたのか(Issuer)、などを内容を見て確認し、メッセージの内容が改ざんされていないかどうか、あらかじめ登録されているアプリケーションサービスの証明書を用いて検証します。改ざんされていない(=つまり発行者が確実に作成したメッセージである)ことが確認できたら、メッセージに記載されているユーザの情報(Subject)を用いてアクセス許可を行います。
つまり、Salesforceは外部サービスが主張するユーザ情報をそのまま信用してアクセスを許可します。もっと言うと、外部のアプリケーションサービスは、Salesforceのどのユーザアカウントでログインするか、ということを自分たちで自由に指定できる、ということになります。
先ほどJWT Bearer Tokenフローは「事前に信頼関係を構築できるシステムからSalesforceへAPI接続する場合に有効」と書きましたが、それはこのような理由からでした。
メリットとデメリット
JWT Bearer Tokenフローを用いる理由としては以下の点が考えられるでしょう
- Webブラウザ画面を介して承認させる必要がない
- 接続するユーザごとに永続的なクレデンシャル情報(トークンやパスワード)を管理する必要がない
最初の項目については、サーバ間での連携などブラウザ環境やユーザの介在が難しい場合に有効です。あらかじめSalesforce側で接続アプリケーションの設定さえしておけば、外部アプリケーションサービスは接続したいユーザの情報を記したJWTを作成してSalesforce側に渡せば、いつでもAPIへのアクセストークンが手に入ります。
また2番目については、セキュアに管理するべきものは署名のための秘密鍵情報のみになるので、接続が必要なすべてのユーザに対してリフレッシュトークンであったりパスワードであったりをセキュアに保管する必要はなくなります。特に、外部アプリケーションサービス側から各Salesforceユーザのアイデンティティ情報を維持してアクセスを行わなければならない場合など、接続情報の管理が大変簡単になります。
反対に、考慮しておくべき点としては以下の点があるでしょう
- 接続アプリケーションとして事前に密な信頼関係を結んでおく必要がある
- 証明書の発行、秘密鍵の管理をセキュアに行う必要がある
最初の項目については、前もってSalesforce組織が接続アプリケーションを信用している必要がある、ということなので、不特定多数のSalesforce組織に対してサービスを行うようなアプリケーションについては、そもそもこのフローは向いていません。通常のOAuth2のAuthorization Code/Implicitフローなどで連携するのが適切かと思います。
また2番目については、他のメリットとのトレードオフになるかと思います。とはいえ一点に安全管理を集中できるのは通常システムの運用面からは好ましいことですし、秘密鍵の漏洩時においても証明書を再発行する等で対処は簡単に可能です。
接続方法
さて、それでは実際にJWT Bearer Tokenフローを用いてSalesforceからアクセストークンを取得して接続するアプリケーションを作成してみましょう。
まず最初に、JWTの電子署名を行うための秘密鍵および証明書を作成します。
以下、OpenSSLを用いてRSA公開暗号鍵ペアを生成し、自己証明書を作成するコマンド例です。
$ openssl genrsa 2048 > myapp.pem
$ openssl req -new -key myapp.pem -out myapp.csr
(..snip..)
$ openssl x509 -req -days 365 -in myapp.csr -signkey myapp.pem -out myapp.crt
続いて、接続するSalesforce組織の管理者アカウントでログインし、設定画面から接続アプリケーションを作成します。接続アプリケーションを作成する際には、下の画像のように「API (OAuth 設定の有効化)」セクションで、「デジタル証明書」項目に先ほど作成した証明書ファイル(myapp.crt)を選択してアップロードします。
また「選択したOAuth 範囲」では、「ユーザに変わっていつでも要求を実行(refresh_token, offline_access)」を必ず含めるようにします。
なおコールバックURLは、今回は使うことはありませんので、無難にSalesforceの用意しているコールバック先アドレスを入力しておきます。
接続アプリケーションを作成したら、作成した接続アプリケーションの「Manage」をクリックし、さらに詳細画面から「編集」ボタンを押します。
編集画面の「OAuthポリシー」セクションで、「許可されているユーザ」として「管理者が承認したユーザは事前承認済み」を選び、保存します。
続いて接続アプリケーションの詳細画面で、「プロファイル」関連リストから、接続を許可するユーザのプロファイルを選び、リストに追加します。これによりこの接続アプリからログインできるユーザを事前に設定しておくことができるようになります。
最後に、SalesforceにAPI接続するアプリケーションを作成します。下記はNode.jsでJWTを生成し、JWT Bearer Token フローを用いて取得したアクセストークンをもとにJSforceを用いてAPIコールを行う例です。
var fs = require('fs');
var jwt = require('jsonwebtoken');
var request = require('request');
var jsforce = require('jsforce');
var TOKEN_ENDPOINT_URL = 'https://login.salesforce.com/services/oauth2/token';
var ISSUER = '3MVG9ZL0ppGP5....RRkh8B'; // 接続アプリのコンシューマ鍵(client_id)
var AUDIENCE = 'https://login.salesforce.com'; // 固定
var cert = fs.readFileSync('./path/to/myapp.pem'); // 秘密鍵の読み込み
// JWTに記載されるメッセージの内容
var claim = {
iss: ISSUER,
aud: AUDIENCE,
sub: 'user01@mydomain.example.org', // 接続するSalesforceのユーザアカウント名
exp: Math.floor(Date.now() / 1000) + 3 * 60 //現在時刻から3分間のみ有効
};
// JWTの生成と署名
var token = jwt.sign(claim, cert, { algorithm: 'RS256'});
// JWT Bearer Token フローによるアクセストークンのリクエスト
request({
method: 'POST',
url: TOKEN_ENDPOINT_URL,
form: {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: token
}
}, function(err, response, body) {
if (err) {
return console.error(err);
}
var ret = JSON.parse(body);
var conn = new jsforce.Connection({
accessToken: ret.access_token,
instanceUrl: ret.instance_url
});
conn.query('SELECT Id, Name FROM Account LIMIT 5').then(function(qr) {
console.log('Done:', qr.done);
console.log('Fetched Records:', qr.records);
});
});
まとめ
以上、ちょっと変わった形ですがSalesforceへの接続が実現できました。別に各ユーザごとに接続のための証明情報を用意しなくても、あらかじめ構築したサーバ間の信頼関係によってシングルサインオンが実現できるということがわかる良い例かと思います。
また、おなじような連携方法としてOAuth2 SAML Bearer Assertion Flowというのもありますが、原理はほぼ同じです。ただ、SAMLアサーションよりもJWTのほうが動作環境的な制約はすくないので、特別な事情のない限りJWTでの利用のみを想定しておけば問題はないでしょう。