mcpField
A
auth

auth

github.com/adobeセキュリティ3161.5k 回インストール今日更新
リポジトリ

auth について

AEM Edge Delivery Services の管理操作に使う認証トークンを取得するスキルです。組織とサイトから ID プロバイダーを特定し、ブラウザーでログインした後、複数プロジェクトで使えるようユーザー単位でトークンを保存します。

主な機能

  1. 01組織とサイトから ID プロバイダーを特定
  2. 02ブラウザーでログインしローカルコールバックで受信
  3. 03ユーザー単位でトークンをキャッシュ
  4. 04管理 API 用トークンの利用

活用シーン

  1. 01プレビューや公開の前に認証
  2. 02401 応答後のトークン更新
  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 Authentication

すべての Edge Delivery Services 管理操作で使用するトークンを取得するために認証します。org と site から ID プロバイダーを自動検出するため、コンテンツソースを尋ねる必要はありません。ログイン時にユーザーの既定のブラウザーを開き、ローカルのコールバックサーバーを介してトークンを受け取ります。

トークンの使用方法

authToken は管理 API で使用できます。

APIヘッダー用途
admin.hlx.pagex-auth-token: ${AUTH_TOKEN}プレビュー、公開、ステータス、コード同期、ジョブ、ログ、設定
Config Servicex-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 が一致しない別のプロセスが不正なコールバックを送信した可能性があります。ログインを再実行してください

仕組み

  1. CLI がランダムな localhost ポートで一時的な HTTP サーバーを起動します
  2. ユーザーのデフォルトブラウザーで、client_id=aem-cli と redirect_uri=http://localhost:{port}/... を指定して admin.hlx.page/login/{org}/{site}/main を開きます
  3. Admin API が IDP(Google、Microsoft、Adobe)を自動検出し、ログイン画面にリダイレクトします
  4. ユーザーが実際のブラウザーで認証します
  5. ログイン後、Admin API に「Send」ボタンがある確認ページが表示されます
  6. ユーザーが「Send」をクリックすると、ブラウザーが { state, authToken } を localhost に POST します
  7. CLI が state nonce を検証し、authToken を ~/.aem/ims-token.json に保存して終了します

統合

呼び出し元:ops、handover-admin、handover-author、handover-developer、handover

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

コメント