APIのバックドア
kintone で API を使用する際、API を介してアクセスする各アプリの権限は、そのアカウントに対するアクセス権限に依存します。
例えば、在庫マスターアプリと在庫一覧アプリが存在し、従業員 A さんは在庫一覧アプリにのみアクセスできる設定がされているとします。
この場合、A さんのアカウントで実行された JavaScript にも同様のアクセス権限が適用されるため、在庫マスターアプリにアクセスするような JavaScript が実行されたとしても、アクセスすることができずエラーとなります。
このため現在の仕様では、JavaScript からだけアクセスしたい場合でも、そのアカウントにアクセスできる権限を付与する必要があります。
しかし、それぞれのアカウントにアプリは表示したくないけど、API だけ利用したい。(ユーザは閲覧することができないが、内部的にはアクセスしたい)といった場合もあるかと思います。
今回はこの対処方法を紹介します。
kintone が本来想定している使い方から外れた方法のため、アップデートにより突然実行できなくなる可能性があります。また、API トークンを JavaScript のコードに記述すると、そのカスタマイズを読み込めるユーザーなら誰でもトークンを確認できます。トークンには必要最小限の権限(例: 閲覧のみ)だけを付与し、機密性の高いデータには使用しないでください。
結論
まず結論からお伝えすると、kintone JavaScript API のkintone.proxyメソッドを使って、同ドメインの REST API にアクセスすることで実現可能です。
公式の cybozu developer network には、
※APIトークンはREST APIを実行するためのトークンであるため、JavaScript API内では利用できないので注意してください。
とありますが、同じく kintone が用意している proxy API を使用すれば動作します。
kintone REST API の認証方法には ID、パスワードを使う方法と、API トークンを使用する方法があります。
今回の例に関わらず、セキュリティリスクを抑えたいのであれば、REST API を利用する場合は API トークンによる認証を使うべきです。
API トークンについて
REST API の認証には、ユーザー ID・パスワードによる方法のほか、アプリ単位で発行される API トークンを使う方法があります。
API トークンを利用することで、より的を絞った使用が可能になります。
公式にも記載されていますが、API トークンを使うことで、以下のようなメリットがあります。全て、機能を絞ることによるセキュリティリスクの軽減と言えます。
- ユーザ ID とパスワードが必要ない
- 限られたアプリにのみ、REST API を実行できる
- 実行できる REST API の種類を制限することができる
ID、パスワードによる認証方法だと、どうしてもアカウント情報が分散されてしまうため、メンテナンス性も、セキュリティもあまりよくありません。全権限を使用したい場合でも、基本的には API トークンを利用した認証方法が望ましいと思います。
アクセス権限のないアプリにアクセスする方法
本題ですが、冒頭でお話した、それぞれのアカウントにアプリは表示したくないけど、API だけ利用したい
といった場合の実装方法です。
/**
* kintone.proxyを使用して、権限のないアプリにアクセスします
* @param {{ path: string; method: "GET" | "POST" | "PUT" | "DELETE"; param: Record<string, any>; apiToken: string }} props 各パラメータ(pathは `/k/v1/records.json` のような REST API のパス)
* @return { Promise<[string, number, object]> } [レスポンス本文(JSON文字列), ステータスコード, レスポンスヘッダー]
*/
const useBackdoor = (props) => {
const { path, method, param, apiToken } = props;
const headers = { 'X-Cybozu-API-Token': apiToken };
let uri = kintone.api.url(path, true);
let body = {};
if (method === 'GET') {
// GET の場合はクエリ文字列として付与する
uri += `?${new URLSearchParams(param).toString()}`;
} else {
headers['Content-Type'] = 'application/json';
body = param;
}
return kintone.proxy(uri, method, headers, body);
};
使用例です。kintone.proxy の返り値はレスポンス本文が文字列のため、JSON.parse で変換します。
const [body, status] = await useBackdoor({
path: '/k/v1/records.json',
method: 'GET',
param: { app: 1, query: 'limit 10' },
apiToken: 'xxxxxxxx', // 対象アプリで発行した API トークン
});
if (status !== 200) {
throw new Error(`取得に失敗しました: ${body}`);
}
const { records } = JSON.parse(body);
処理としては、kintone から一度外に出て、別の kintone にアクセスするようなイメージです。