auth について
AEM Edge Delivery Services の管理操作に使う認証トークンを取得するスキルです。組織とサイトから ID プロバイダーを特定し、ブラウザーでログインした後、複数プロジェクトで使えるようユーザー単位でトークンを保存します。
主な機能
- 01組織とサイトから ID プロバイダーを特定
- 02ブラウザーでログインしローカルコールバックで受信
- 03ユーザー単位でトークンをキャッシュ
- 04管理 API 用トークンの利用
活用シーン
- 01プレビューや公開の前に認証
- 02401 応答後のトークン更新
- 03サイト設定や管理 API へのアクセス
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ adobe/ skills --skill authインストール後、Claude Code で/authを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.releaserc.jsonCHANGELOG.mdpackage.json詳細
AEM Edge Delivery Services Authentication
すべての Edge Delivery Services 管理操作で使用するトークンを取得するために認証します。org と site から ID プロバイダーを自動検出するため、コンテンツソースを尋ねる必要はありません。ログイン時にユーザーの既定のブラウザーを開き、ローカルのコールバックサーバーを介してトークンを受け取ります。
トークンの使用方法
authToken は管理 API で使用できます。
| API | ヘッダー | 用途 |
|---|---|---|
admin.hlx.page | x-auth-token: ${AUTH_TOKEN} | プレビュー、公開、ステータス、コード同期、ジョブ、ログ、設定 |
| Config Service | x-auth-token: ${AUTH_TOKEN} | サイト、設定、シークレット、API キー、プロファイル |
注:
admin.da.liveでは、Authorization: Bearerヘッダーを使用する別の Adobe IMS トークンが必要です。DA 固有のログインフローについては、ops/resources/da.mdを参照してください。
このスキルを使用するタイミング
- API が 401 Unauthorized を返す場合
- ユーザーが「ログイン」「認証」「auth」と言った場合
- トークンがない、または有効期限切れの場合、管理操作の前に
- API アクセスが必要なガイドを生成する前に
前提条件
- Node.js がインストールされていること
認証フロー
ステップ 1:既存のトークンを確認する
トークンはユーザーレベル(~/.aem/ims-token.json)にキャッシュされ、すべてのプロジェクトで共有されます。
mkdir -p "${HOME}/.aem"
AUTH_TOKEN=$(node -e "
const fs = require('fs');
try {
const t = JSON.parse(fs.readFileSync(process.env.HOME + '/.aem/ims-token.json', 'utf8'));
if (t.authToken && t.authTokenExpiry > Math.floor(Date.now()/1000) + 60) {
process.stdout.write(t.authToken);
}
} catch (e) {}
")
if [ -n "$AUTH_TOKEN" ]; then
echo "Token valid"
exit 0
fi
echo "Token missing or expired. Starting login..."
ステップ 2:Org と Site を特定する
自動ログインエンドポイント /login/{org}/{site}/main は、適切な ID プロバイダーに自動的にリダイレクトするため、コンテンツソースを把握する必要はありません。
利用可能な情報源(project-config、ops-config、git remote)から org と site を特定します。
# Try project-config first (handover context)
ORG=$(cat .claude-plugin/project-config.json 2>/dev/null | node -e "
const d = require('fs').readFileSync(0,'utf8');
try { process.stdout.write(JSON.parse(d).org || ''); } catch(e) {}
")
# Fallback to ops-config (ops context)
if [ -z "$ORG" ]; then
ORG=$(node -e "
const fs = require('fs');
try {
const c = JSON.parse(fs.readFileSync(process.env.HOME + '/.aem/ops-config.json', 'utf8'));
process.stdout.write(c.org || '');
} catch(e) {}
")
fi
# Site: try git remote first, then ops-config
SITE=$(basename -s .git $(git remote get-url origin 2>/dev/null) 2>/dev/null)
if [ -z "$SITE" ]; then
SITE=$(node -e "
const fs = require('fs');
try {
const c = JSON.parse(fs.readFileSync(process.env.HOME + '/.aem/ops-config.json', 'utf8'));
process.stdout.write(c.site || '');
} catch(e) {}
")
fi
echo "org=${ORG:-NOT SET} site=${SITE:-NOT SET}"
ORG が空の場合は、ユーザーに尋ねます。
「認証するには、組織名が必要です。次のいずれかを入力してください。
- org 名(
https://main--site--{org}.aem.pageの{org})https://main--mysite--myorg.aem.page/のようなプレビュー/ライブ URL」
ユーザーが URL を入力した場合は、そこから org と site を解析します。
URL="$USER_INPUT"
if echo "$URL" | grep -q '\.aem\.page\|\.aem\.live'; then
HOST_PART=$(echo "$URL" | cut -d'/' -f3 | cut -d'.' -f1)
ORG=$(echo "$HOST_PART" | awk -F'--' '{print $NF}')
SITE=$(echo "$HOST_PART" | awk -F'--' '{print $(NF-1)}')
echo "Parsed from URL: org=$ORG site=$SITE"
fi
SITE も空の場合(git リポジトリ内ではなく、URL も入力されていない場合)は、ユーザーに尋ねます。
「ログインプロバイダーを自動検出するため、サイト名も必要です。サイト名を教えてください(
https://main--{site}--{org}.aem.pageの{site}の部分)。」
org と site の両方が特定できるまで、処理を進めないでください。
ステップ 3:ループバックリダイレクトでトークンを取得する
ログイン時にユーザーの既定のブラウザーを開きます。ログイン完了後、一時的なローカル HTTP サーバーがトークンのコールバックを受け取ります。トークンを送信するには、ユーザーが確認ページの「送信」をクリックする必要があります。すべての ID プロバイダー(Adobe IMS、Google、Microsoft)で動作します。
ユーザー向けメッセージ(以下のスクリプトを実行する前に表示):
{org}/{site}へのログイン用にブラウザーを開きました。認証後に「送信」をクリックしてください。完了したらタブを閉じてもかまいません。
指示がはっきり目立つように、太字または強調表示の書式を使用してください。
mkdir -p "${HOME}/.aem"
node -e "
const http = require('http');
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const TOKEN_PATH = path.join(process.env.HOME, '.aem', 'ims-token.json');
const ORG = '${ORG}';
const SITE = '${SITE}';
const STATE = crypto.randomUUID();
const server = http.createServer((req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
if (req.method === 'POST') {
let body = '';
req.on('data', (chunk) => { body += chunk; });
req.on('end', () => {
try {
const parsed = JSON.parse(body);
if (parsed.state !== STATE) {
console.error('State mismatch — ignoring callback');
res.writeHead(403);
res.end('State mismatch');
return;
}
const authToken = parsed.authToken;
if (authToken) {
const expiresAt = Math.floor(Date.now() / 1000) + 86400;
fs.writeFileSync(TOKEN_PATH, JSON.stringify({
authToken,
authTokenExpiry: expiresAt,
}, null, 2));
try { fs.chmodSync(TOKEN_PATH, 0o600); } catch (e) {}
console.log('Authentication successful');
console.log('Token cached at: ' + TOKEN_PATH);
console.log('Expires: ' + new Date(expiresAt * 1000).toISOString());
} else {
console.error('No authToken in callback. The helix-admin authToken support may not yet be deployed.');
console.error('Keys received: ' + Object.keys(parsed).join(', '));
}
res.writeHead(200, { 'content-type': 'text/plain' });
res.end('OK');
} catch (e) {
console.error('Failed to parse callback: ' + e.message);
res.writeHead(400);
res.end('Bad request');
}
server.close(() => process.exit(0));
});
} else if (req.method === 'GET') {
res.writeHead(200, { 'content-type': 'text/html' });
res.end('<html><body><p>Login complete. You can close this tab.</p><script>window.close()</script></body></html>');
}
});
server.listen(0, () => {
const port = server.address().port;
const redirectUri = 'http://localhost:' + port + '/.aem/cli/login/ack';
const loginUrl = 'https://admin.hlx.page/login/' + ORG + '/' + SITE + '/main?client_id=aem-cli&redirect_uri=' + encodeURIComponent(redirectUri) + '&state=' + STATE + '&selectAccount=true';
console.log('Opening browser for login...');
console.log('URL: ' + loginUrl);
console.log('');
console.log('After logging in, click the Send button to complete authentication.');
try { execSync('open \"' + loginUrl + '\"'); } catch (e) {
try { execSync('xdg-open \"' + loginUrl + '\"'); } catch (e2) {
console.log('Could not open browser. Please open this URL manually:');
console.log(loginUrl);
}
}
});
setTimeout(() => {
console.error('Login timed out after 5 minutes. No callback received.');
process.exit(1);
}, 300000);
"
トークンの保存
ユーザーレベルのトークンキャッシュ — ~/.aem/ims-token.json:
{
"authToken": "eyJ...",
"authTokenExpiry": 1780489855
}
| フィールド | 説明 |
|---|---|
authToken | ログインコールバックから取得した管理用 JWT |
authTokenExpiry | トークンの有効期限を示す Unix タイムスタンプ(約 24 時間) |
このマシン上のすべてのプロジェクトで共有されます。ファイルは 0600 権限で書き込まれます。
トークンの使用
# Read token from user-level cache
AUTH_TOKEN=$(node -e "
const fs = require('fs');
try {
const t = JSON.parse(fs.readFileSync(process.env.HOME + '/.aem/ims-token.json', 'utf8'));
process.stdout.write(t.authToken || '');
} catch (e) {}
")
# admin.hlx.page and Config Service use the same header
curl -H "x-auth-token: ${AUTH_TOKEN}" "https://admin.hlx.page/status/{org}/{site}/main/"
curl -H "x-auth-token: ${AUTH_TOKEN}" "https://admin.hlx.page/config/{org}/sites.json"
トラブルシューティング
| 問題 | 解決方法 |
|---|---|
| ブラウザーが開かない | ターミナルに表示された URL を手動で開きます |
| 「送信」ボタンが機能しない | ブラウザーのコンソールでエラーを確認し、広告ブロッカーが localhost リクエストをブロックしていないことを確認します |
| コールバックに authToken がない | CLI のログイン応答に authToken を追加する helix-admin の PR がまだデプロイされていません |
| トークンを受信できない | 5 分のタイムアウト前に確認ページの「送信」をクリックしたことを確認します |
| ログイン後に 401 が発生する | トークンの有効期限が切れています。再認証してください |
| API で 403 が発生する | ユーザーにその org/site の権限がありません |
| State が一致しない | 別のプロセスが不正なコールバックを送信した可能性があります。ログインを再実行してください |
仕組み
- CLI がランダムな localhost ポートで一時的な HTTP サーバーを起動します
- ユーザーのデフォルトブラウザーで、
client_id=aem-cliとredirect_uri=http://localhost:{port}/...を指定してadmin.hlx.page/login/{org}/{site}/mainを開きます - Admin API が IDP(Google、Microsoft、Adobe)を自動検出し、ログイン画面にリダイレクトします
- ユーザーが実際のブラウザーで認証します
- ログイン後、Admin API に「Send」ボタンがある確認ページが表示されます
- ユーザーが「Send」をクリックすると、ブラウザーが
{ state, authToken }を localhost に POST します - CLI が state nonce を検証し、
authTokenを~/.aem/ims-token.jsonに保存して終了します
統合
呼び出し元:ops、handover-admin、handover-author、handover-developer、handover
Skill({ skill: "aem-project-management:auth" })