SwitchBot API v1.1をPostmanから呼び出す

# はじめに
去る2022年9月にSwitchBot APIのv1.1がリリースされました。v1.0のサポートは現在も続いていますが、今後の新デバイス対応はじめ機能拡張は停止されているためv1.1への移行が強く推奨されています。SwitchBot APIを使ってホームオートメーションを楽しんでいるIoTホビイストの皆さんは徐々に移行を開始されているか、あるいはまだ手をつけられずにいるのではないでしょうか。
この記事では、v1.1における最大の変更点であるAPI認証を、REST APIテストツールとして人気のPostmanでどのように実装すればよいか解説していきます。

# API認証
SwitchBot API v1.1における最大の変更ポイントがAPI認証方式の仕様変更です。

> In SwitchBot API v1.1, the authentication method has been improved. In order to gain access to private data through the API, you must generate a unique signature using a token and a secret key. When you make a request, the Authorization token and signature will be validated simultaneously.

(https://github.com/OpenWonderLabs/SwitchBotAPI#authentication より)

従来のv1.0ではトークンをヘッダーで送信するだけでしたが、新しいv1.1ではトークンとシークレットキーから動的にsignature(署名)を生成して送信する必要があります。公式ドキュメントには、Python, JavaScript, C#などメジャーな言語における実装方法のサンプルコードが掲載されているのでそれらを参考にすればすんなり通信できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184078/4cfe8605-d952-75e2-46cb-dcd18bab47cc.jpeg" width=200>

```JavaScript:Sample JavaScript code from SwithBot API document
const token = "yourToken";
const secret = "yourSecret";
const t = Date.now();
const nonce = "requestID";
const data = token + t + nonce;
const signTerm = crypto.createHmac('sha256', secret)
    .update(Buffer.from(data, 'utf-8'))
    .digest();
const sign = signTerm.toString("base64");
console.log(sign);
```
(JavaScriptのサンプルコードを上記公式ドキュメントから一部引用)

# Postmanのような便利ツールではどうする？
ここで本題となる、Postmanのような便利ツールではどうすれば良いかについてです。v1.0では素朴にヘッダーのAuthorizationキーにアプリからコピーしたトークンを貼り付けて送信すれば永久に使い続けられました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184078/b6cc41e6-8002-5335-f622-ee275c820f09.png)
しかし、v1.1ではセキュリティ強化によりクライアントは算出したsignature(sign)とともに計算に使用したUnixTime(t)と任意文字列(nonce)も送信する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184078/dee787e3-f5bb-c9c7-e7f2-5069de318cb0.png)
上記は便利なぺんたん様のこちら(https://pentan.info/iot/switchbot/make_api_request.html) のサイトを使って算出しテストしたときの値ですが、上記のような即値はおよそ5分以内に使用できなくなることが実験でわかりました。詳細は不明ですが、クライアントから送信するUnixTime(t)も認証に使用されていることは明らかです。
したがって、Postmanのようなツールにおいても、動的にヘッダーの値を算出して送信する必要があります。

# Pre-request script (リクエスト前スクリプト)
Postmanではこのようなユースケースに対応したPre-request script (リクエスト前スクリプト)という機能があります。ここでは例として「SwitchBot API」という名称のCollectionが作成してあり、そのCollection内の全requestに共通のPre-request scriptを用意する手順を紹介します。

まず、前準備として、グローバル変数を作成します。Postmanの画面右上にある[Environment quick look]をクリックしてください。
![Screenshot 2023-05-05 10.39.55 - Display 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184078/772c88e6-ad5d-ef22-bfd9-a832002b6c67.png)
するとこのような変数を定義できるダイアログが開くので、token, sign, nonce, tという変数を作成してください。Environmentを作成し、Environmentスコープの変数を作成しても構いません。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184078/c5bf5d0f-8788-b0fd-5c15-f16580321e84.png" width=600>
次に、ツリー内のCollection名（ここでは「SwitchBot API」）をクリックし、さらに[Pre-request Script]をクリックしてください。
![Screenshot 2023-05-05 10.52.25 - Display 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184078/541f2042-918e-6ea9-0655-ada9e2977c87.png)
開いたエディットボックス内にリクエストが実行される前に実行されるJavaScriptを記述していきます。動作確認済みのサンプルコードはこちらです。コードを書き終えたら右上の[Save]ボタンで保存してください。
```JavaScript: Common Pre-request script for a collection
const token = "92708d6a34f4ea9f..."; // token
const secret = "00f394fc46f732b6..."; // secret
const t = Date.now(); // UnixTime
const uuid = require('uuid');
const nonce = uuid.v4(); // use UUID as an arbitrary string
const message = token + t + nonce;
const sign =
    CryptoJS.enc.Base64.stringify(
        CryptoJS.HmacSHA256(message, secret)
    );

pm.globals.set("token", token);
pm.globals.set("sign", sign);
pm.globals.set("nonce", nonce);
pm.globals.set("t", t);
```
最後の `pm.globals.set()` で定義済みのグローバル変数に値を書き込んでいます。なお、SwitchBot公式のサンプルコードを流用しただけではうまく動作せず、Postman向けに変更が必要だったポイントをまとめておきます：
- cryptoパッケージやcrypto-jsパッケージをインポートしてもhmacやbase64のメソッドがうまく呼び出せないため、組み込みの `CryptoJS.HmacSHA256()` と `CryptoJS.enc.Base64.stringify()` を使用する
- `data` という変数は組み込みの変数か定義とバッティングしているようなので `message` など別の名前に変える

最後にPostmanのリクエスト編集画面でヘッダーの値として即値ではなく変数を使用するように変更します。1文字目として'{'と入力すると変数を候補から簡単に選択できます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184078/f2252446-ae8f-b913-73e3-3651969cb029.png" width=600>
[Send]をクリックしてrequestを送信すると、期待通りAPIが動作することを確認できました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184078/13574881-b680-4da1-5d14-25493a769027.png" width=300>

# おまけ
HTTP Shortcuts for Androidというとても便利なアプリがあります。
https://http-shortcuts.rmy.ch/
いわば「スマホ上のCurl」といった感じでREST APIを呼び出すショートカットをホーム画面上に作ることができるようになり、SwitchBotのシーン機能だけでは実現できない制御をSwitchBotアプリを開かずにワンタップで行いたいときに重宝しています。
(SwitchBot v7.0以降ではアプリのホーム画面にシーンへのショートカットを追加できるようになったので純正の機能でも操作の手間は多少軽減されました)

この「HTTP Shortcuts」に関しても上記のPostmanと同様にAPI v1.1に対応するにあたり動的にヘッダーを生成しなければいけない問題にぶつかったのですが、ありがたいことに同様のスクリプト実行機能が搭載されているため難なく対応することができました。以下に動作確認済みのサンプルコードを掲載しておきます。やはりアプリ組み込みのメソッドをいくつか呼び出す必要があるため参考にしてください。
```Javascript:Global Scripting.js for HTTP Shortcuts
const token = "92708d6a34f4ea9f..."; // token
const secret = "00f394fc46f732b6..."; // secret
const t = Date.now();
const nonce = uuidv4();
const data = token + t + nonce;
const sign = base64encode(hmac('SHA-256', secret, data));

setVariable("token", token);
setVariable("sign", sign);
setVariable("nonce", nonce);
setVariable("t", t);
```