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.jsonexpires_at2025-12-04T18:17:33.110929+09:00 のような形式で入っているので、最初はそれを利用しようと考えました。TZ=UTC の環境だと 2025-12-04T09:17:33.110929Z になります。
時刻の比較をするには unixtime にする方が都合が良いので変換したかったんですが、jqdate コマンドもこの形式の 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 した方がよっぽど楽ではとなって最終的なコードが完成しました。

広告
AWS ユーザに送る Azure 権限管理 (RBAC) 入門