PostmanでJWT認証方式を使用してBox APIを呼べるようにしてみました。
前置き
Box APIでは複数の認証方式があり、公式ではOAuth 2.0を使用したPostmanコレクションが提供されています(手順はこちら)。
https://ja.developer.box.com/guides/tooling/postman/
ここでは、JWT認証を使用してPostmanからBox APIを呼ぶ方法について記載します。
Boxアプリケーションの作成・承認
Box APIを呼ぶにあたり、事前にBoxのカスタムアプリの作成と承認を行います。
- まず、Boxの開発者コンソールからJWT認証方式のアプリを作成します。詳しい手順については、公式ページに記載があるのでこちらでは割愛します。
- Box管理者としてログインし、#1で作成したアプリの承認を行います。こちらも公式ページに手順があるので割愛します。
なお、#1でアプリを作成する際の「キーペアの生成」手順でダウンロードされる config.json(以下のような形式でJWTアプリの認証に必要となる情報が含まれたファイル)は後述の手順で使用します。
{
"boxAppSettings": {
"clientID": "abc...123",
"clientSecret": "def...234",
"appAuth": {
"publicKeyID": "abcd1234",
"privateKey": "-----BEGIN ENCRYPTED PRIVATE KEY-----\n....\n-----END ENCRYPTED PRIVATE KEY-----\n",
"passphrase": "ghi...345"
}
},
"enterpriseID": "1234567"
}
Postmanのインストール・設定
- Postmanを公式よりダウンロードしインストールします。
- こちらのPostman Collectionをインポートします。
- Postmanで「Import」をクリック ->「Link」のタブでURLを入力し「Continue」をクリック ->「Box API with JWT」を「Import」
- もしくは、こちらのボタンからコレクションをForkまたImport
- Postmanで「Import」をクリック ->「Link」のタブでURLを入力し「Continue」をクリック ->「Box API with JWT」を「Import」
- Collectionがインポートされたら、そのCollectionのタイトル部分をクリックし、Collection環境変数(当Collection内のみで有効の変数です)を設定します。
- "config_json" の[CURRENT VALUE]欄に先のBoxアプリ作成の手順でダウンロードされた config.json の内容をそのままコピペします。
注意: [INITIAL VALUE]欄には絶対貼り付けないでください。こちらにセットされた値はサーバーとの同期の対象になります。 - 「INIT: Load crypto library for RS512」をクリックし「Send」を実行します。これにより "jsrsasign_js" の環境変数の値が更新されます。
- "config_json" の[CURRENT VALUE]欄に先のBoxアプリ作成の手順でダウンロードされた config.json の内容をそのままコピペします。
これで準備完了です。
Box APIを呼んでみる
それでは、Box APIを呼んでみましょう。「Get current user」を実行し、以下のようにサービスアカウントの情報(AutomationUser_AppServiceID_RandomString@boxdevedition.com
)が返ってきていれば成功です!
How it works
ここでは仕組みについて解説します。ざっくり言いますと、PostmanのPre-request Scriptという機能を使用して、リクエストを実行する手前で以下のロジックにて認証処理を行うことでAPIコールが行えるようにしてあります。
基本的には公式の「SDKを使用しないJWT」に記載のNode.jsのコードを踏襲してます。
1. config.jsonの情報を環境変数から取得
const envConfig = pm.collectionVariables.get('config_json')
const config = JSON.parse(envConfig);
2. 外部ライブラリのインポート
JWTアサーションの生成にあたり、JWTクレームに署名をするための暗号化ライブラリを使用したいのですが、残念ながらPre-request ScriptのプラットフォームとなるPostman Sandbox上では標準提供されていないため、require
でインポートすることができません。
回避策(詳細はこちら)として、環境変数として外部のライブラリのコードを丸ごと定義し、その環境変数をロードするという方法を使用します。それが以下の処理です。
// Load the jsrsasign library into Postman Sandbox
const navigator = {}; //fake a navigator object for the lib
const window = {}; //fake a window object for the lib
eval(pm.collectionVariables.get("jsrsasign_js")); //import javascript jsrsasign
ちなみに、前述の「INIT: Load crypto library for RS512」を実行する手順は、jsrsasignのレポジトリにあるコードをCollection環境変数"jsrsasign_js"へと書き込む処理です。
3. JWTアサーションの生成
jsrsasignにて秘密鍵でJWTクレームを署名し、JWTアサーションを作成します。
// Generate random string for "jti" claim
let newJti = "";
const charset = "abcdefghijklmnopqrstuvwxyz0123456789";
// At Box, it must be at least 16 characters and at most 128 characters
// Ref: https://developer.box.com/guides/authentication/jwt/without-sdk/#3-create-jwt-assertion
for( let i=0; i < 16; i++ ) {
newJti += charset.charAt(Math.floor(Math.random() * charset.length));
}
// Create Header and Payload objects
const authenticationUrl = "https://api.box.com/oauth2/token";
let header = {
"kid": config.boxAppSettings.appAuth.publicKeyID,
"alg": 'RS512'
};
let payload = {
iss: config.boxAppSettings.clientID,
sub: config.enterpriseID,
box_sub_type: "enterprise",
aud: authenticationUrl,
jti: newJti,
exp: Math.floor(Date.now() / 1000) + 45
};
const key = {
key: config.boxAppSettings.appAuth.privateKey,
passphrase: config.boxAppSettings.appAuth.passphrase
};
const prvKey = KEYUTIL.getKey(key.key, key.passphrase);
// Prep the objects for a JWT
const sHeader = JSON.stringify(header);
const sPayload = JSON.stringify(payload);
const sJWT = KJUR.jws.JWS.sign(header.alg, sHeader, sPayload, prvKey);
4. アクセストークンをリクエスト
JWTアサーションを送信しアクセストークンと交換します。
pm.sendRequest({
url: 'https://api.box.com/oauth2/token',
method: 'POST',
headers: { 'Content-Type': 'Content-Type: application/x-www-form-urlencoded' },
body: {
mode: 'urlencoded',
urlencoded: [
{ key: 'client_id', value: config.boxAppSettings.clientID, disabled: false },
{ key: 'client_secret', value: config.boxAppSettings.clientSecret, disabled: false },
{ key: 'assertion', value: sJWT, disabled: false },
{ key: 'grant_type', value: 'urn:ietf:params:oauth:grant-type:jwt-bearer', disabled: false }
]
}
}, function (error, response) {
if (error || response.json().error) {
// if an error occured, log the error and raise a message to the user.
console.log(error)
console.log(response.json())
throw new Error('Could not get the access token. Check the console for more details.')
} else {
// otherwise, fetch the new access token and store it
const data = response.json()
// determine when this token is set to expire at
const newExpiresAt = Date.now() + data.expires_in * 1000
// store the new variables in the environment
pm.collectionVariables.set('jwt_access_token', data.access_token)
pm.collectionVariables.set('jwt_expires_at', newExpiresAt)
}
})
レスポンスで返ってきたアクセストークンはCollection環境変数"jwt_access_token"へと保存され、実際のリクエストが送信される際に使用されます(またトークンの有効期間内は次回以降のリクエスト実行時にも使用されます)。
なお、トークンを失効させたい場合は、「Revoke access token」を実行することで可能です。このリクエストを実行すると、こちらのAPIが送信されトークンが失効された後、リクエスト後の処理(Testsスクリプト)で以下が実行され、Collection環境変数からも削除されます。
pm.collectionVariables.unset("jwt_expires_at");
pm.collectionVariables.unset("jwt_access_token");