・OAuth2.0トークン認証によるREST API連携 ・OAuth2.0クライアント認証によるREST API連携★ ・トークンベース認証によるRestlet API連携
本日はOAuth2.0クライアント証明書認証によるREST API連携方法とハマったポイントなどをご紹介したいと思います。
アーキテクチャ構成
Komawoの前身であるCBA(CUEBiC Analytics)から先行的に ドメインとして持つべきではないマスタをNetSuiteに退避し、REST APIにて同期を行いました。 こちらのアーキテクチャでは前回のOAuth2.0認証ではリフレッシュトークンの有効期限が短く、本番運用では利用できないという欠点を克服しました。
事前準備
前提
インテグレーションの発行が完了している前提になります。インテグレーションの発行方法は 前回のOAuth2.0認証をご参照ください。
事前に以下の準備が必要です
1.クライアント証明書の発行 2.NetSuite上でのクライアント証明書の設定
1.クライアント証明書の発行
NetSuiteの公式サイト上で指定されているopen sslで発行を行います
openssl req -x509 -newkey rsa:4096 -sha256 -keyout auth-key.pem -out auth-cert.pem -nodes-days730
上記のコマンドをターミナルなるなどで入力すると、対話形式で入力を求められます。 質問内容と解答例を示します。
質問内容 | 解答例(例です) |
---|---|
Country Name | JP |
State or Province Name | Tokyo |
Locality Name | Shinjyuku-ku |
Organization Name | sample company |
Organizational Unit Name | sample department |
Common Name | sample.co.jp |
Email Address | 不要 |
上記設定値を設定していくとローカルに以下の2ファイルが生成されます
ファイル名 | 概要/用途 |
---|---|
auth-cert.pem | publicファイル(CERTIFICATE) NetSuite上に登録 |
auth-key.pem | private key JWTの生成で使用 |
参考にさせていただいたリンク 自己署名証明書の作成方法 qiita.com
2.NetSuite上でのクライアント証明書の設定
次にNetSuite上にクライアント証明書を登録します
設定>インテグレーション>OAuth2.0クライアント資格証明(M2M)設定へすすむ
1.新規作成ボタンを押下 2.エンティティで紐づけたいアカウントを選択 3.ロールは:「Administrator」など高位のロールを選択 4.アプリケーション:インテグレーションで設定したものを選択 5.証明書ファイルを選択:1.クライアント証明書の発行で生成した「auth-cert.pem」を選択 6.保存ボタンを押下して問題なければOK
成功すると以下のように証明書がNetSuite上に設定されます
APIの設定
以下の順番で説明していきます
1.JWTの発行 2.アクセストークンの取得
1.JWTの発行
アクセストークンを取得するためのパラメータの「client_assertion」がJWT方式で生成しないといけません。 NetSuiteの公式で指定されているリクエストトークン構造でトークンを発行します。
『JWTとは?』
JSON WEB TOKENの略 以下の3つの構成でカンマ区切りでトークンを発行する ・トークンヘッダー ・ペイロード ・クライアント証明のCERTIFICATE トークンヘッダーとペイロードに認証情報を含んでおり、クライアントはその情報を元に 情報を複合して一致していた場合に認証やトークン発行を行なっている
トークンヘッダー
パラメータ | 設定値 |
---|---|
typ | JWT(固定) |
alg | RS256(HS256は使用できないので注意) |
kid | 「2.NetSuiteでのクライアント証明書の設定」で登録したクライアント証明書のID |
トークンペイロード
パラメータ | 設定値 |
---|---|
iss | インテグレーションで発行したクライアントID |
scope | rest_webservices |
aud | NetSuiteのtokenのエンドポイント |
exp | UNIX時間で設定/トークンの有効期限 ※過去日付はNG ※iatより1時間以内であること |
iat | UNIX時間で設定/トークン発行日時 ※過去日付はNG |
NetSuiteのtokenのエンドポイントの例
https://<accountID>.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token
JWTの生成の実装サンプル
公式がチラッとライブラリーが必要な旨を記載しています・・・ 残念ながらJWTは普通に生成できません。
ここでは、node.jsを使用した生成例を解説します。
Node.jsのjsonwebtokenライブラリを使用します
『選定理由』
- ヘッダーとパラメーターの適合率が高かった
- 即席で実装できるたから
『苦労した点』
- 微妙にパラメータ名が違って苦しみました。マッピングを解説します。
- 気になる方は以下を解析してみてください node-jsonwebtoken/sign.js at master · auth0/node-jsonwebtoken
トークンヘッダー
NetSuite | ライブラリー |
---|---|
typ | 設定不要。JWTが固定で設定される |
alg | algorithm |
kid | keyid |
トークンペイロード
NetSuite | ライブラリー |
---|---|
iss | iss |
scope | なし |
aud | audience |
exp | exp |
iat | 設定不要:timestampが設定される |
ソースコード
Node.jsは入っている前提になります。
以下のライブラリーをインストールします
npm install --save sleep npm install --save @types/sleep
「creat_jwt.js」をnode.jsがあるディレクトリと同じディレクトリに作成します
var fs = require('fs') var jwt = require('jsonwebtoken') const private_key = fs.readFileSync('./auth-key.pem', 'utf8') const public_key = fs.readFileSync('./auth-cert.pem', 'utf8')トークンの発行と発行トークンの認証確認を行いますconst jwtheader = { algorithm: 'RS256', keyid: 'your kid ' };
const jwtPayload = { iss: 'your iss', scope: 'rest_webservices', aud: 'your audience', exp: 1654498589, //The value must not be more than one hour greater than the value of the iat parameter. };
const token = jwt.sign( jwtPayload , {key: private_key ,passphrase:'passphrase here'}, jwtheader) console.log( '=== JWT TOKEN RS256==='); console.log(token.toString('base64')); const verified = jwt.verify(token, public_key , { algorithms: 'RS256'}, function(err, decoded) { if (err) { // 認証NGの場合 console.log(err.message); } else { // 認証OKの場合 console.log(decoded); } });
node creat_jwt.js
成功したらこんな感じの結果が得られます(生成値は正しくないものに変えています)
=== JWT TOKEN RS256=== eyJhbGciOiJSUzI1NiIsInR5cCeyJpc3MiOiI0OTljYjM3MzkyYTM2ZjEyN2IzYjNiYjk3NDAzM2FiNmI5YjRmYzE2M2E5YTM5MjZlNjk4MDc2MDRjNzJjNjQ1Iiwic2NvcGUiOiJyZXN0X3dlYnNlcnZpY2VzIiwiYXVkIjoiaHR0cHM6Ly81Mzg1NzI3LnN1aXRldGFsay5hcGkubmV0c3VpdGUuY29tL3NlcnZpY2VzL3Jlc3QvYXV0aC9vYXV0aDIvdjEvdG9rZW4iLCJleHAMTY1NDQ5NTEzMH0.KL26d4vO827ki6zGwqKMoD4HEUxx1TNi6iGhj17-WPnKpekqxp-HFSk_rddynMPZz_vm2HBnNKxoRlQKh831QoTOB_9jcjsR33GMeMP-smgcf3NZRxVrvBUufph9XHHTmS9ZcvbAf0XQGijVzDB0GZBMmdAMUtQdkG0Lh9hmhr1IR8Mu1P7Tgys-eAfhenb-N5GbsjON-f4BeEao-dUxKvsbkphNSFsEBrO7xvRTJN2VywKwPFScFh89GVYPS1VLYaBjw4xj4_APLYCOtUKp9JPXAfoTaB1hztjQ44GdggT1k5h42cY0qO1_X6JDPDs-mvPLSuQ6kmyW5txRe217vFFoviutQS4zyy5HCEbQUA6ik7ThI7ITd4-sGiVZtaUtWzDOaNOmllrtmpV1eSRbpTHjr5ydxt_6aXgV889sFw0kgiP6zhTkCFPo7Xd6dS8fYucRbWXOzJfJG-7NB-qpKxsEvtb1VXHDNMqXspoPTT-r8Zw2RA6eqh74H0VGYUJL0_Lsll541k9df6E { iss: 'your iss', scope: 'rest_webservices', aud: 'your aud', exp: 1654498589, iat: 1654495130 }
2.アクセストークンの取得
JWTが取得できたので、ようやくアクセストークンの取得へと移ります。
公式で公開されているサンプル
リクエスト形式:POST エンドポイント:https://<accountID>.app.netsuite.com/services/rest/auth/oauth2/v1/token?
POST /services/rest/auth/oauth2/v1/token HTTP/1.1 Host: <accountID>.suitetalk.api.netsuite Content-Type: application/x-www-form-urlencoded grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiIxYzM0M2E3MTZjMWRjZWI2MGU3ZmMxNDlmYTY3MzU5MjllZjc3ZDI4ZmUxNjI5M2Y4OTI5NzZkZGU3ZDhlM2UyIiwic2NvcGUiOlsicmVzdGxldHMiLCAicmVzdF93ZWJzZXJ2aWNlcyJdLCJhdWQiOiJodHRwczovL3J1bmJveC5jb3JwLm5ldHN1aXRlLmNvbS9zZXJ2aWNlcy9yZXN0L2F1dGgvb2F1dGgyL3YxL3Rva2VuIiwiZXhwIjoxNjI3OTA5MzAzLCJpYXQiOjE2Mjc5MDU3MDN9.j7fhtd0qQP-iD7ns9q_fuG8Arz2aWJyoSvZ8sHRVA8HXOJG3pAQbT5J5F8MLkWIXA9ZuSxHdCWNwQLoRUeKlGURYFFqDHP_yjoWFWWtq5Wb-AnaZg_jBVL8TaOFGY2WByFM8rHsJVopFegwEQsU6bkcwqiFttEKxso-MiSAc5lE9SBgi6Fus2btiYGIFcNrKalFXEWDy6Ah5yVCo3wxkk9dfiPmT6JgLdjFkCc3v7tMCD9CrRHXrmhQvL8aoeyTMzJILURw5rnuy9zAs9ngymtX_iiwes8XpkBeCJbX4totI-EY4myi7L4fc2NgeWT-bvLWo6_sWjXE4BKyewqjtreUJscR9bhJ5Fi7S8nIoGDQbZrwhIgoKM_UI9Waw6kRLwRer_c0QDFY-sMLeGT3HL5vihHRFNXd-cKb-AWplkRiSJrdHXJtuGHLniHRpkK0-A1AFalIzYw4SSykxfck0qsPdf-oFPuawUsKR9lDCcYlyOaDZdQsBNsbjOsp5gGtyCuBwPBS8xz7I6gqLVEfNuzTfDDk8SMw1fN9MQ0NJtZMqMxm-WY_bLjZVkI3gqsvgDS-ADBPC7cymVZGfPUqummDUeG-Ks7SkLaHpfY6i-aZS8KUAY4aN5Do3GWT56aoEM9s1YB_1ZF_YxsBmK_gcX_mmlwUxbvCVpuHJTvKAQzY
パラメータ
項目 | 設定値 |
---|---|
grant_type | client_credentials |
client_assertion_type | urn:ietf:params:oauth:client-assertion-type:jwt-bearer |
client_assertion | JWTの生成で発行したトークン |
ヘッダー
項目 | 設定値 |
---|---|
Content-Type | application/x-www-form-urlencoded |
レスポンス
項目 | 設定値 |
---|---|
access_token | アクセストークン |
Expires_in | アクセストークンの有効時間常に3600秒(1時間) |
token_type | 常に「Bearer」 |
curlコマンドなどで確認してみましょう
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion={your client_assertion}' https://{accountID}.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token
成功すると以下のようなレスポンスが返ってきます(access_tokenは適当な値に直しています)
{"access_token":"eyJraWQiOiJjLjUzODU3MjcuMjAyMi0wNC0yNF8xOS00My00OCIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiIzOzgyMCIsImF1ZCI6WyIxRTZEMTI2My0wODIxLTREM0YtQkU1My1FQTk0ODVEQzY4MDM7NTM4NTcyNyIsIjQ5OWNiMTY1NDUwMDI1NSwiZXhwIjoxNjU0NTAzODU1LCJpYXQiOjE2NTQ1MDAyNTUsImp0aSI6IjUzODU3MjcuYS1jLm51bGwuMTY1NDUwMDI1NTUyNSJ9.tbpaJ8xF6Gmpy_NClWszHaSX1t3wiyICzvew9MuCbTI5ut3totCdzV6UIIa7Yyg2gbNm2eL37RGhK4NaAFwMbuxXx6snL6dXaUG5NsfODfCRD3XZIyUA7At1VUUDxycULnkVh5Mf7zGWZvvs5QwASA0itF6B0y5FZ8vpq-Spog021m6K4Y2Y9Vs-uC3nBAc4DUgkK8hFXlf_KxN2A5562mm-UyGrtjG9ZJvrFupxSHwyVQHhyxs35aykwlddbPjLUZbApS6_3GQndIkaqxrWwdFTVsACryfJfUB8F8hYNU","expires_in":"3600","token_type":"Bearer"}
はまった点
『 クライアント証明書のやってはダメなこと』
- 証明書の有効期限が切れている(最長2年です)
- 証明書を再発行した上でNS上に再度アップロードしてください
- 証明書に紐づくアカウントのロールが証明書設定時のものではないものに変更された
- 証明書を新規ロールで作り直すor元のロールを再度割り当ててください
- 証明書に紐づくインテグレーションが削除または変更された
- インテグレーションを再設定した上で、証明書を再発行してください
『JWT(jason web token)の発行で困ったこと』
- JWTのGENERSTERサイト
- 割とJWT界隈では有名らしいです・・・が、
- ヘッダーとペイロードがライブラリーで保証されていませんでした。
- NetSuiteの認証要求に耐えられないので断念しました。
- トークン発行後にコピペしたらパラメータの複合はできたので私のやり方の問題かも・・・
『NetSuiteのinternal_idが連番ではない』
- API連携と直接関係しない話ですがNetSuiteではレコードのナンバリングにinternal_idを使用しています
- このIDが曲者で、ある段階から急に100→500に飛んだりするのでマスタを退避して設定している際にRDBのIDと乖離が起きました
- 一括インポートでやれば大丈夫と思ったそこのあなた!新規レコードの追加時も毎回洗い替えしますか?
- 結果として同期する際にRDBのスキーマにinternal_idに相当するカラムを追加する必要が出てきました。
課題
『 更新トリガー』
- 現状はNetSuiteの更新をトリガーに同期をしているわけではありません
- 理想系
- NetSuiteの更新トリガーで登録/更新レコードのみが同期される
次回はこちらの課題も考慮した「トークンベース認証によるRestlet API連携」をご紹介します。