关于 auth
用于获取 AEM Edge Delivery Services 管理操作令牌的技能。它根据组织和站点识别身份提供方,通过浏览器登录,并将令牌缓存在用户级别供多个项目使用。
主要功能
- 01根据组织和站点识别身份提供方
- 02通过浏览器登录并接收本地回调
- 03在用户级别缓存令牌
- 04支持用于管理 API 的令牌
适用场景
- 01预览或发布前进行身份验证
- 02收到 401 响应后刷新令牌
- 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 身份验证
进行身份验证以获取令牌,用于所有 Edge Delivery Services 管理操作。根据组织和站点自动检测身份提供方——无需询问内容源。打开用户的默认浏览器进行登录,并通过本地回调服务器接收令牌。
令牌使用方式
authToken 可用于管理 API:
| API | Header | 用途 |
|---|---|---|
admin.hlx.page | x-auth-token: ${AUTH_TOKEN} | 预览、发布、状态、代码同步、作业、日志、配置 |
| Config Service | x-auth-token: ${AUTH_TOKEN} | 站点、配置、密钥、API 密钥、配置文件 |
注意:
admin.da.live使用单独的 Adobe IMS 令牌,并通过Authorization: BearerHeader 传递。请参阅ops/resources/da.md中针对 DA 的登录流程。
何时使用此技能
- API 返回 401 Unauthorized
- 用户说“登录”“进行身份验证”或“身份验证”
- 缺少令牌或令牌已过期时,在执行任何管理操作之前
- 在生成需要 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 步:确定组织和站点
自动登录端点 /login/{org}/{site}/main 会自动重定向到正确的身份提供方——无需了解内容源。
从可用来源(project-config、ops-config、git remote)确定组织和站点:
# 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 为空,请询问用户:
“我需要你的组织名称才能进行身份验证。你可以提供以下任一信息:
- 组织名称(
https://main--site--{org}.aem.page中的{org})- 预览/正式环境 URL,例如
https://main--mysite--myorg.aem.page/”
如果用户提供 URL,请从中解析组织和站点:
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}部分)”
在组织和站点信息都可用之前,请勿继续。
第 3 步:通过回环重定向获取令牌
打开用户的默认浏览器进行登录。登录完成后,临时本地 HTTP 服务器会接收令牌回调。用户必须在确认页面上点击“发送”以传递令牌。此流程适用于所有身份提供方(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 | 添加将 authToken 纳入 CLI 登录响应的 helix-admin PR 尚未部署 |
| 未收到令牌 | 确保在 5 分钟超时前点击了确认页面上的“发送” |
| 登录后出现 401 | 令牌已过期,请重新进行身份验证 |
| API 返回 403 | 用户没有该组织/站点的权限 |
| 状态不匹配 | 其他进程可能发送了恶意回调;请重新运行登录流程 |
工作原理
- CLI 在随机的 localhost 端口启动临时 HTTP 服务
- 在用户的默认浏览器中打开
admin.hlx.page/login/{org}/{site}/main,并附带client_id=aem-cli和redirect_uri=http://localhost:{port}/... - Admin API 自动检测身份提供商(Google、Microsoft、Adobe),并重定向到登录页面
- 用户在自己的浏览器中完成身份验证
- 登录后,Admin API 显示一个确认页面,其中有一个“发送”按钮
- 用户点击“发送”——浏览器将
{ state, authToken }POST 到 localhost - CLI 验证 state nonce,将
authToken保存到~/.aem/ims-token.json,然后退出
集成
由以下命令调用:ops、handover-admin、handover-author、handover-developer、handover
Skill({ skill: "aem-project-management:auth" })