mcpField
A
auth

auth

github.com/adobe安全3161.5k 次安装今天更新
代码仓库

关于 auth

用于获取 AEM Edge Delivery Services 管理操作令牌的技能。它根据组织和站点识别身份提供方,通过浏览器登录,并将令牌缓存在用户级别供多个项目使用。

主要功能

  1. 01根据组织和站点识别身份提供方
  2. 02通过浏览器登录并接收本地回调
  3. 03在用户级别缓存令牌
  4. 04支持用于管理 API 的令牌

适用场景

  1. 01预览或发布前进行身份验证
  2. 02收到 401 响应后刷新令牌
  3. 03访问站点配置和管理 API

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/adobe/skills --skill auth

安装后在 Claude Code 中输入/auth确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

.releaserc.json
CHANGELOG.md
package.json

详细介绍

AEM Edge Delivery Services 身份验证

进行身份验证以获取令牌,用于所有 Edge Delivery Services 管理操作。根据组织和站点自动检测身份提供方——无需询问内容源。打开用户的默认浏览器进行登录,并通过本地回调服务器接收令牌。

令牌使用方式

authToken 可用于管理 API:

APIHeader用途
admin.hlx.pagex-auth-token: ${AUTH_TOKEN}预览、发布、状态、代码同步、作业、日志、配置
Config Servicex-auth-token: ${AUTH_TOKEN}站点、配置、密钥、API 密钥、配置文件

注意: admin.da.live 使用单独的 Adobe IMS 令牌,并通过 Authorization: Bearer Header 传递。请参阅 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用户没有该组织/站点的权限
状态不匹配其他进程可能发送了恶意回调;请重新运行登录流程

工作原理

  1. CLI 在随机的 localhost 端口启动临时 HTTP 服务
  2. 在用户的默认浏览器中打开 admin.hlx.page/login/{org}/{site}/main,并附带 client_id=aem-cli 和 redirect_uri=http://localhost:{port}/...
  3. Admin API 自动检测身份提供商(Google、Microsoft、Adobe),并重定向到登录页面
  4. 用户在自己的浏览器中完成身份验证
  5. 登录后,Admin API 显示一个确认页面,其中有一个“发送”按钮
  6. 用户点击“发送”——浏览器将 { state, authToken } POST 到 localhost
  7. CLI 验证 state nonce,将 authToken 保存到 ~/.aem/ims-token.json,然后退出

集成

由以下命令调用:ops、handover-admin、handover-author、handover-developer、handover

Skill({ skill: "aem-project-management:auth" })

评论

支持 Markdown