Auth0 の Deploy CLI Tool で Auth0 CLI の access token を利用する
Auth0 の Deploy CLI Tool では Create a dedicated Auth0 application で説明されているように Machine to Machine Application を作成し、その client ID/secret を使うのが一般的です。
CI/CD などに組み込む場合はその方法が良いでしょうが、開発者一人ひとりが Auth0 の設定をエクスポートしたい場合などに client ID/secret を利用するのは、開発者体験的にも流出のリスク的にも避けたいものです。
そこで、Auth0 CLI は Machine to Machine Application を作成せずとも Auth0 のリソースにアクセスできることから、Auth0 CLI で利用している access token を利用できるのではと考えました。
Auth0 CLI の access token を使って export するサンプルコード
というわけで、完成したのが以下のコードです。
#!/bin/bash
set -euo pipefail
ADDITIONAL_SCOPES=read:guardian_factors,read:mfa_policies,read:self_service_profiles
get_access_token() {
local token=""
if ! token=$(jq -er '.tenants["'$domain'"].access_token' $config_file); then
token=$(get_access_token_from_keychain)
fi
echo $token
}
# Retrieves an access token from the macOS keychain.
# This mimics the way the Auth0 CLI stores tokens, based on:
# - https://github.com/auth0/auth0-cli/blob/v1.23.0/internal/keyring/keyring.go#L96-L113
# - https://github.com/zalando/go-keyring/blob/v0.2.6/keyring_darwin.go#L42-L67
get_access_token_from_keychain() {
local access_token=""
for i in {0..49}; do
if ! out=$(security find-generic-password -s "Auth0 CLI Access Token $i" -wa $AUTH0_DOMAIN 2>&1); then
if [[ $out == *"could not be found"* ]]; then
echo $access_token
return 0
else
echo $out >&2
return 1
fi
fi
# go-keyring-base64:ZXlK... => ZXlK... => eyJ...
access_token+=$(cut -d: -f2 <<< $out | base64 -d)
done
echo $access_token
}
decode_jwt() {
# https://gist.github.com/angelo-v/e0208a18d455e2e6ea3c40ad637aac53?permalink_comment_id=3439919#gistcomment-3439919
jq -R 'gsub("-";"+") | gsub("_";"/") | split(".") | .[1] | @base64d | fromjson' <<< $1
}
# The Auth0 CLI exits with status 0 on SIGINT, so trap it to stop the script.
trap exit INT
domain=${AUTH0_DOMAIN:?Error: AUTH0_DOMAIN is required}
config_file=~/.config/auth0/config.json
if [ -f $config_file ]; then
if ! jq -e '.tenants["'$domain'"]' $config_file > /dev/null; then
auth0 login --domain $domain --scopes $ADDITIONAL_SCOPES
fi
else
auth0 login --domain $domain --scopes $ADDITIONAL_SCOPES
fi
token=$(get_access_token)
if [ -n "$token" ]; then
payload=$(decode_jwt $token)
exp=$(jq '.exp' <<< $payload)
require_additional_scopes=$(jq '($ARGS.positional[0] | split(",")) - (.scope | split(" ")) | length > 0' --args $ADDITIONAL_SCOPES <<< $payload)
fi
if [ -z "$token" ] || [ $(date +%s) -ge $exp ] || $require_additional_scopes; then
auth0 login --domain $domain --scopes $ADDITIONAL_SCOPES
token=$(get_access_token)
fi
trap - INT
AUTH0_ACCESS_TOKEN=$(get_access_token) a0deploy export --format=yaml -o .
Auth0 CLI の access token を取得する処理はコメントにもあるとおり次の箇所が肝になっていて、それを模倣しています。
Linux に関しては docker container で試した感じだと ~/.config/auth0/config.json に access token が保存されたので深くは処理を追っていません。環境によっては追加の処理が必要かもしれません。
余談: access token の expiration を考慮するのが地味に大変
完成したコードは割とシンプルなコードになっているんじゃないかと思いますが、expiration を考慮するのが大変でした。
access token の expiration は ~/.config/auth0/config.json の expires_at に 2025-12-04T18:17:33.110929+09:00 のような形式で入っているので、最初はそれを利用しようと考えました。TZ=UTC の環境だと 2025-12-04T09:17:33.110929Z になります。
時刻の比較をするには unixtime にする方が都合が良いので変換したかったんですが、jq も date コマンドもこの形式の timestamp は簡単には処理できません。
jq で timestamp を unixtime に変換することの問題点
まず、fromdateiso8601 がミリ秒をサポートしていません。
$ jq -n '"2025-12-04T18:17:33.110929+09:00" | fromdateiso8601'
jq: error (at <unknown>): date "2021-02-16T15:36:29+0000" does not match format "%Y-%m-%dT%H:%M:%SZ"
cf. Parsing of milliseconds in dates · Issue #1409 · jqlang/jq
次に、timezone offset をサポートしていません。
$ jq -n '"2025-12-04T18:17:33.110929+09:00" | sub("\\.[0-9]+"; "") | fromdateiso8601'
jq: error (at <unknown>): date "2025-12-04T18:17:33+09:00" does not match format "%Y-%m-%dT%H:%M:%SZ"
$ jq -n '"2025-12-04T18:17:33.110929+09:00" | sub("\\.[0-9]+"; "") | strptime("%Y-%m-%dT%H:%M:%S%z") | mktime'
jq: error (at <unknown>): date "2025-12-04T18:17:33+09:00" does not match format "%Y-%m-%dT%H:%M:%S%z"
cf. fromdate should support timezone offsets · Issue #1053 · jqlang/jq
strptime は timezone offset からコロンを取り除けば Mac ではエラーにはなりません。ただ、結果がおかしいです。
$ jq -n '"2025-12-04T18:17:33.110929+09:00" | sub("\\.[0-9]+"; "") | strptime("%Y-%m-%dT%H:%M:%S%z") | mktime'
jq: error (at <unknown>): date "2025-12-04T18:17:33+09:00" does not match format "%Y-%m-%dT%H:%M:%S%z"
$ jq -n '"2025-12-04T18:17:33.110929+0900" | sub("\\.[0-9]+"; "") | strptime("%Y-%m-%dT%H:%M:%S%z") | mktime'
1764872253
$ date -r1764872253
Fri Dec 5 03:17:33 JST 2025
Linux だとコロンがあってもなくてもエラーにはならないものの offset が無視されます。
$ jq -n '"2025-12-04T18:17:33.110929+0900" | sub("\\.[0-9]+"; "") | strptime("%Y-%m-%dT%H:%M:%S%z") | mktime'
1764872253
$ jq -n '"2025-12-04T18:17:33.110929+09:00" | sub("\\.[0-9]+"; "") | strptime("%Y-%m-%dT%H:%M:%S%z") | mktime'
1764872253
$ jq -n '"2025-12-04T18:17:33.110929+00:00" | sub("\\.[0-9]+"; "") | strptime("%Y-%m-%dT%H:%M:%S%z") | mktime'
1764872253
$ jq -n '"2025-12-04T18:17:33.110929+0000" | sub("\\.[0-9]+"; "") | strptime("%Y-%m-%dT%H:%M:%S%z") | mktime'
1764872253
cf. strptime/1 ignores ISO-8601 TimeZone (format string “%z”) · Issue #2195 · jqlang/jq
というわけで、jq を利用することは諦めました。
date で timestamp を unixtime に変換することの問題点
Linux の date コマンドであれば次のように簡単に処理できます。
$ date -d 2025-12-04T18:17:33.110929+0900 +%s
1764839853
ところが、BSD の date コマンドだと一筋縄ではいきません。ミリ秒情報を削除して、timezone offset からコロンを取り除く必要があります。
$ date -j -f '%FT%T%z' 2025-12-04T18:17:33+0900
Thu Dec 4 18:17:33 JST 2025
timezone 情報が Z の場合も別の処理が必要になります。
$ date -j -f '%FT%T%z' 2025-12-04T18:17:33Z
Failed conversion of ``2025-12-04T18:17:33Z'' using format ``%FT%T%z''
date: illegal time format
usage: date [-jnRu] [-I[date|hours|minutes|seconds]] [-f input_fmt]
[-r filename|seconds] [-v[+|-]val[y|m|w|d|H|M|S]]
[[[[mm]dd]HH]MM[[cc]yy][.SS] | new_date] [+output_fmt]
$ date -j -f '%FT%T%Z' 2025-12-04T18:17:33Z
Failed conversion of ``2025-12-04T18:17:33Z'' using format ``%FT%T%Z''
date: illegal time format
usage: date [-jnRu] [-I[date|hours|minutes|seconds]] [-f input_fmt]
[-r filename|seconds] [-v[+|-]val[y|m|w|d|H|M|S]]
[[[[mm]dd]HH]MM[[cc]yy][.SS] | new_date] [+output_fmt]
Linux の場合と Mac の場合で分岐をして、Mac の場合はミリ秒やコロンを取り除いて、更に timezone offset か Z かで分岐をして…とするぐらいなら、JWT を decode した方がよっぽど楽ではとなって最終的なコードが完成しました。