ZAF の Secure Settings を使って認証方式別に疎通確認してみた
はじめに
先方から提供された Basic 認証付きのエンドポイントに、ZAF (Zendesk Apps Framework) アプリから繋ぐ場面がありました。最初は認証が 401 で通らず少し戸惑ったのですが、原因は ZAF の Secure Settings の仕組みを私が誤解していたことでした。
secure 設定の値は JavaScript に返りません。プレースホルダは文字列置換のみで、base64 化まではしません。この 2 点を押さえると、認証付き API へスムーズに繋げられます。
本記事では、認証付き API に ZAF から複数の認証方式で疎通確認した結果と、そこから得たノウハウをまとめます。
対象読者
- ZAF アプリから認証付きの外部 API に繋ぎたい方
- Secure Settings を使っているのに認証が通らず悩んでいる方
参考
起きた問題
最初は、認証情報のユーザー名とパスワードを別々の設定として持ち、フロントエンドで Authorization ヘッダを組み立てていました。'Basic ' + base64(user + ':' + password) のような書き方です。このとき client.request の secure オプションは付けていませんでした。
これはローカルでは動作しました。ところがテナントへデプロイし、secure 設定に実値を入れたところ、認証付き API が 401 になり「認証情報を確認してください」というエラーが消えなくなりました。また、サーバが返す WWW-Authenticate にブラウザが反応し、ネイティブの Basic 認証ダイアログが出ました。

原因調査
公式ドキュメントを読み直して、原因が分かりました。secure: true を宣言した設定値は、client.metadata() 経由でフロントエンドには返りません。
正しい方法は、secure 値をリクエストに乗せるのに、ヘッダに {{setting.NAME}} というプレースホルダを書き、client.request の secure を true にすることでした。こうすることで Zendesk のプロキシがサーバ側で実値に置換します。フロントエンドで base64 を組み立てる必要はない、むしろ組み立ててはいけないということになります。
疎通確認
認証エンドポイントに httpbingo.org を使い、ZAF の client.request() で 5 通りの認証方式を試しました。結果は次のとおりです。
| # | 認証方式 | 結果 | 分かったこと |
|---|---|---|---|
| 1 | Basic・自前 base64 を 1 つの secure 値 | 成功 (200) | 事前に base64 化して 1 設定に入れれば通る |
| 2 | Basic・basic_auth.token | 成功 (200) | プロキシが base64 するので自前変換は不要 |
| 3 | Bearer トークン | 成功 (200) | プレースホルダ + secure でそのまま通る |
| 4 | API キー ヘッダ | 成功 (200) | scope 未指定でも通る・明示は推奨 |
| 5 | フロントエンドで base64 生成 | 失敗 (401) | 置換前に base64 され、認証ダイアログも出る |

サイドバーアプリを使用した疎通確認。ケース 1~3

サイドバーアプリを使用した疎通確認。ケース 3~5
ケース 1 は、user:passwd を自分で base64 化した値を 1 つの secure 設定に入れ、ヘッダにはプレースホルダだけを書く方式です。
// Basic 認証・自前で base64 化した値を 1 つの secure 設定に入れる
client.request({
url: 'https://httpbingo.org/basic-auth/user/passwd',
type: 'GET',
secure: true,
headers: { Authorization: 'Basic {{setting.basicManualB64}}' }
});
ケース 2 は、公式が用意する basic_auth の仕組みです。ユーザー名とパスワードを渡し、ヘッダには {{basic_auth.token}} を書くと、プロキシが連結して base64 化します。自分で base64 化する必要がありません。
// Basic 認証・basic_auth.token 方式・プロキシが base64 化する
client.request({
url: 'https://httpbingo.org/basic-auth/user/passwd',
type: 'GET',
basic_auth: { username: 'user', password: '{{setting.basicPassword}}' },
secure: true,
headers: { Authorization: 'Basic {{basic_auth.token}}' }
});
注目したいのはケース 4 です。httpbingo の /headers はリクエストヘッダをそのまま返すため、プロキシがサーバ側で挿入した後の実値が見えます。このエコーに Zendesk プロキシの痕跡が現れ、リクエストがフロントエンドから直接ではなく、プロキシ経由でサーバ側から送られていることが分かります。
X-Api-Key にはプロキシが挿入した実値が入り、Via や X-Zendesk-* に Zendesk プロキシの痕跡が出ています。
{
"headers": {
"X-Api-Key": ["(プロキシが挿入した実値)"],
"Via": ["zorg, 1.1 fly.io, 1.1 fly.io"],
"X-Zendesk-User-Role": ["admin"],
"X-Requested-With": ["XMLHttpRequest"],
"Zendesk-Internet-Path": ["cloudflare"]
}
}
一方で、ブラウザの開発者ツールの Network タブでは、同じヘッダが {{setting.apiKey}} というプレースホルダのまま表示されます。実値はプロキシより先にしか出ない、ということが確かめられます。

ブラウザにはプレースホルダしか出ず、実値はサーバ側で挿入される
なぜフロントエンドで base64 化してはいけないのか
ケース 5 は、最初に私が書いていた実装を再現したものです。フロントエンドで btoa() を使い、プレースホルダを base64 化してからヘッダに入れます。
// 避けたい書き方・プレースホルダを btoa で base64 化してしまう
client.request({
url: 'https://httpbingo.org/basic-auth/user/passwd',
type: 'GET',
secure: true,
headers: { Authorization: 'Basic ' + btoa('{{setting.basicManualB64}}') }
});
btoa() はプロキシの置換より前に走るため、プレースホルダの文字列そのものが base64 化されて送られます。プロキシが探す {{setting.NAME}} の形が壊れるので置換されず、誤った認証情報のまま届いて 401 になります。
さらに、この 401 が WWW-Authenticate を伴うため、client.request 経由でもブラウザのネイティブ Basic 認証ダイアログが出ます。認証が通らないうえに、ユーザーに認証ダイアログを見せてしまいます。

フロントエンドで base64 化すると認証が通らず、ダイアログまで出てしまう
分かったこと
疎通確認から得たノウハウは次のとおりです。
secure 値はフロントエンドに返らないため、認証情報をフロントエンドで組み立てる設計は避けます。Basic 認証は、自分で base64 化した値を 1 つの secure 設定に入れるか、basic_auth.token 方式でプロキシに base64 化させます。Bearer や API キーは、プレースホルダ + secure でそのまま通ります。
運用面では、scope を明示指定するのが推奨されます。指定しないと secure 値が意図しない scope で使われる恐れがあります。また、secure 設定は保存後に Admin Center の入力欄が空表示になりますが、保存されていないわけではありません。

管理センター→Zendesk Supportアプリ→作成したアプリ を選択し、secure 設定を入力して更新する

更新後、入力欄は空表示になる
まとめ
ZAF の Secure Settings は、secure 値がフロントエンドに返らず、プレースホルダは文字列置換のみという設計です。この 2 点を押さえれば、認証付き API へ繋げられます。Basic 認証は自分で base64 化するか basic_auth.token 方式を使うというのが疎通確認で確かめられました。同じところで戸惑っている方の参考になれば幸いです。







