תיעוד 144
אימות זהות סשן בסביבות באירוח עצמי
אמתו את ה-JWT של
CLAUDE_CODE_SESSION_ACCESS_TOKENכדי ששירותים ברשת שלכם יוכלו לסמוך על בקשות מסשנים בסביבה באירוח עצמי שלכם.
[!NOTE]
סביבות באירוח עצמי נמצאות בבטא ציבורית בתוכניות Team ו-Enterprise. בעלים מפעיל אותן על ידי הדלקת Allow self-hosted environments בדף הניהול Cloud environments. הדף הזה עוסק באימות זהות סשן. ראו את ההתחלה המהירה להתקנה ואת פריסה לייצור למתכוני הצי.
סביבה באירוח עצמי מאפשרת לסשני ענן של Claude Code לרוץ על תשתית שאתם מפעילים במקום על התשתית של Anthropic. מכיוון שהסשן רץ בתוך הרשת שלכם, Claude יכול לקרוא לשירותים הפנימיים שלכם ישירות. השירותים האלה צריכים דרך לאשר שבקשה הגיעה מסשן של Claude Code בסביבה שלכם, ולזהות את המשתמש או את זהות השירות שיצרו את הסשן.
כל סשן בסביבה באירוח עצמי מקבל JSON Web Token (JWT) חתום במשתנה הסביבה CLAUDE_CODE_SESSION_ACCESS_TOKEN. סשן מציג את האסימון כמו כל אישור bearer. לדוגמה, סקריפט ש-Claude מריץ יכול לקרוא לשירות שלכם עם curl -H "Authorization: Bearer $CLAUDE_CODE_SESSION_ACCESS_TOKEN". Anthropic חותמת על האסימון ומפרסמת את מפתחות האימות בנקודת קצה ציבורית של JWKS. השירותים שלכם שולפים את המפתחות האלה, מאמתים את החתימה, וקוראים את הטענות כדי להחליט איזו גישה להעניק.
#אסימון הסשן
לפני כתיבת קוד האימות, דעו מה האסימון קובע ומה הצורה שספריית ה-JWT תראה.
#מה האסימון מוכיח
אסימון תקין קובע כמה עובדות, ובמכוון לא קובע אחרות:
- מוכיח: Anthropic הנפיקה את האסימון לסשן מסוים בסביבה מסוימת, וכיצד הסשן נוצר: על ידי משתמש בארגון שלכם, או על ידי זהות השירות של הארגון, וכך מתחילים סשני ערוץ Claude Tag
- לא מוכיח: איזה תהליך על מארח ה-runner מציג אותו. האסימון יושב במשתנה סביבה בתוך הסשן, ולכן כל קוד ש-Claude מריץ, וכל כלי או שרת MCP שהסשן מפעיל, יכולים לקרוא אותו ולהציג אותו.
שתי השלכות לשירותים שלכם:
- אמתו את טענת
audמול מזהה הסביבה שלכם, הערךccpool_...שמוצג עם הסביבה שלכם בדף הניהול Cloud environments, כדי לדחות אסימונים שהונפקו לסביבה של כל ארגון אחר. - הגבילו אישורים שנגזרים מהאסימון למה שסשן קידוד יחיד אמור להיות מסוגל לעשות, ולא לכל מה שיוצר הסשן יכול לעשות. ראו היקף אישורים נגזרים.
#מבנה האסימון
הערך של CLAUDE_CODE_SESSION_ACCESS_TOKEN כולל קידומת sk-ant-cc- ואחריה JWT סטנדרטי בן שלושה חלקים:
sk-ant-cc-<base64url header>.<base64url payload>.<base64url signature>הסירו את הקידומת לפני העברת הערך לספריית JWT. אסימונים שהונפקו לסשני ענן באירוח Anthropic נושאים קידומת sk-ant-si- במקום זאת, והם חתומים בסט מפתחות אחר, לכן דחו כל ערך שלא מתחיל ב-sk-ant-cc-.
אלגוריתם החתימה הוא ES256, כלומר ECDSA על עקומת P-256 עם SHA-256. כותרת האסימון נושאת kid שמזהה איזה מפתח ב-JWKS חתם עליו.
#אימות האסימון
האימות רץ באחד משני מקומות. שירותים ברשת שלכם מאמתים את האסימון קריפטוגרפית מול המפתחות ש-Anthropic מפרסמת, וסקריפטי מעטפת בתוך הסשן יכולים להשתמש במפענח המובנה של הבינארי של ה-runner במקום זאת.
#אימות האסימון מהשירות
Anthropic מפרסמת את מפתחות האימות בנקודת קצה ציבורית, בלי אימות:
https://api.anthropic.com/v1/code/.well-known/jwks.jsonהתשובה היא JSON Web Key Set סטנדרטי. Anthropic מחליפה את מפתחות החתימה מדי פעם, ומפתחות מלפני החלפה נשארים בסט מספיק זמן כדי שאסימונים שהם חתמו עליהם ימשיכו לעבור אימות, אז אל תקבעו מפתח יחיד. נקודת הקצה מגדירה Cache-Control: public, max-age=300, ולכן שמירת סט המפתחות במטמון ושליפה מחדש כל חמש דקות בטוחות.
אמתו כל אסימון נכנס מול הבדיקות האלה:
1. בדיקת הקידומת
דחו את הערך אם הוא לא מתחיל ב-sk-ant-cc-, ואז הסירו את הקידומת הזו. השאר הוא JWT קומפקטי סטנדרטי.
2. אימות החתימה
שלפו את ה-JWKS, בחרו את המפתח ש-kid שלו תואם לכותרת האסימון, ואמתו את חתימת ES256. דחו אסימונים שכותרת alg שלהם אינה ES256. אם מגיע אסימון עם kid שאינו בסט המפתחות השמור במטמון, שלפו את ה-JWKS פעם נוספת לפני הדחייה: אחרי החלפה, אסימונים חדשים חתומים במפתח שהסט השמור עדיין לא כולל.
3. אימות המנפיק
דחו את האסימון אם iss אינו בדיוק ccr.
4. אימות הקהל מול הסביבה שלכם
טענת aud היא מערך. דחו את האסימון אלא אם הוא מכיל את מזהה הסביבה שלכם, שבצורתו ccpool_.... מזהה הסביבה מוצג בתיבת הפרטים של הסביבה בדף הניהול Cloud environments, ומופיע כטענה ccr:pool_id בכל אחד מאסימוני הסשן של הסביבה. הבדיקה הזו היא מה שמצמיד את האסימון לסביבה שלכם ודוחה אסימונים שהונפקו לארגונים אחרים.
5. אימות התפקיד
דחו את האסימון אם ccr:role אינו בדיוק session_worker. אסימונים אחרים שמונפקים לסביבות באירוח עצמי, כמו סודות סביבה, אסימוני runner והזמנות עבודה, חתומים באותו סט מפתחות אבל נושאים תפקידים אחרים.
6. אימות תפוגה
דחו את האסימון אם exp בעבר. Anthropic מנפיקה אסימוני סשן עם אורך חיים של ארבע שעות כברירת מחדל ומקסימום של שמונה שעות. ה-runner מרענן את האסימון לפני התפוגה ודוחף את הערך החדש לסשן, כך שתהליכי משנה ש-Claude מפעיל אחרי רענון יורשים אותו. סשן אחד יכול לכן להציג לשירות שלכם כמה אסימונים תקפים נפרדים לאורך חייו.
7. קריאת הזהות
זהות המשתמש היוצר נמצאת בטענת act. act.sub הוא מזהה המשתמש של Anthropic שלו בצורה עם קידומת user:<id>, ו-act.email, כאשר המשטח היוצר רשם כתובת, הוא כתובת הדוא"ל. בסשנים שזהות השירות של הארגון יוצרת, הנושא הוא agent:. התייחסו לסשן כסשן שנוצר על ידי משתמש רק כאשר act.sub נושא את הקידומת user:, ולא על ידי בדיקה אם טענות זהות חסרות.
ראו את אסמכתת הטענות למבנה המלא ולטענות הכפולות השטוחות.
הבדיקות ממופות ישירות על ספריות JWT סטנדרטיות. הדוגמאות למטה מממשות את הרצף המלא ב-Node.js עם jose, שמטפל בשליפת JWKS, שמירה במטמון ובחירת kid, וב-Python עם PyJWT ולקוח ה-JWKS המובנה שלו.
#Node.js (jose)
import { createRemoteJWKSet, jwtVerify } from "jose";
const JWKS = createRemoteJWKSet(
new URL("https://api.anthropic.com/v1/code/.well-known/jwks.json")
);
const PREFIX = "sk-ant-cc-";
const EXPECTED_POOL_ID = "ccpool_...";
export async function verifySessionToken(raw: string) {
if (!raw.startsWith(PREFIX)) {
throw new Error("not a self-hosted runner session token");
}
const jwt = raw.slice(PREFIX.length);
const { payload } = await jwtVerify(jwt, JWKS, {
issuer: "ccr",
audience: EXPECTED_POOL_ID,
algorithms: ["ES256"],
});
if (payload["ccr:role"] !== "session_worker") {
throw new Error("token is not a session_worker token");
}
const act = payload.act as { email?: string; sub?: string };
return {
sessionId: payload["ccr:session_id"] as string,
poolId: payload["ccr:pool_id"] as string,
orgId: payload["ccr:org_id"] as string,
creatorEmail: act?.email,
creatorSub: act?.sub,
};
}#Python (PyJWT)
import jwt
from jwt import PyJWKClient
JWKS_URL = "https://api.anthropic.com/v1/code/.well-known/jwks.json"
PREFIX = "sk-ant-cc-"
EXPECTED_POOL_ID = "ccpool_..."
jwks = PyJWKClient(JWKS_URL)
def verify_session_token(raw: str) -> dict:
if not raw.startswith(PREFIX):
raise ValueError("not a self-hosted runner session token")
token = raw.removeprefix(PREFIX)
signing_key = jwks.get_signing_key_from_jwt(token)
payload = jwt.decode(
token,
signing_key.key,
algorithms=["ES256"],
issuer="ccr",
audience=EXPECTED_POOL_ID,
)
if payload.get("ccr:role") != "session_worker":
raise ValueError("token is not a session_worker token")
act = payload.get("act") or {}
return {
"session_id": payload["ccr:session_id"],
"pool_id": payload["ccr:pool_id"],
"org_id": payload["ccr:org_id"],
"creator_email": act.get("email"),
"creator_sub": act.get("sub"),
}#אימות האסימון מתוך הסשן
סקריפטי מעטפת רצים בתוך הסשן, לפני ש-Claude מתחיל. במקום לקרוא לספריית JWT, הם יכולים להריץ את פקודת המשנה self-hosted-runner decode-token של הבינארי של ה-runner. פקודת המשנה קוראת את האסימון מארגומנט מיקומי, מ-CLAUDE_CODE_SESSION_ACCESS_TOKEN, או מ-stdin בצינור, בסדר הזה, ואז מסירה את הקידומת, מאמתת את החתימה מול נקודת הקצה של ה-JWKS, בודקת תפוגה, ומדפיסה את הטענות כ-JSON. פקודת המשנה מבצעת רק את בדיקות החתימה והתפוגה. היא לא בודקת את iss, את aud או את ccr:role. כאשר החלטת האימות של המעטפת תלויה בטענות האלה, קראו אותן מה-JSON המודפס והשוו אותן במפורש.
הפקודה הזו מחלצת את זהות היוצר, עם עדיפות לכתובת הדוא"ל, ואחר כך לנושא act.sub של היוצר, user:<id> או agent:<id>:
"$CLAUDE_RUNNER_CLAUDE_BIN" self-hosted-runner decode-token | jq -re '.act.email // .act.sub'סקריפטי מעטפת מקבלים את הנתיב המוחלט לבינארי של ה-runner עצמו ב-CLAUDE_RUNNER_CLAUDE_BIN. השתמשו בנתיב הזה ולא ב-claude שנפתר דרך PATH, כדי שהפענוח ירוץ על אותו בינארי שה-runner עצמו משתמש בו.
השתמשו ב-jq -re ולא ב-jq -r, כדי שטענה חסרה תגרום ליציאה עם קוד שאינו אפס. עם -r בלבד, טענה חסרה מדפיסה את המחרוזת המילולית null ויוצאת באפס, מה שמעביר בשקט ערך שגוי הלאה.
אם decode-token לא יכול לשלוף את המפתחות מנקודת הקצה של ה-JWKS או לא יכול לאמת את האסימון, הוא מדפיס את הסיבה ל-stderr, לא מדפיס טענות, ויוצא עם קוד 1. העבירו את --no-verify ל-decode-token רק לבדיקה לא מקוונת שבה נקודת הקצה של ה-JWKS לא נגישה.
#אסמכתת הטענות {#claims-reference}
קראו זהות ממרחב השמות ccr:* ומשרשרת act. הטענות השטוחות account_email, organization_uuid ו-account_uuid הן כפילויות לתאימות לאחור שעשויות להיות מוסרות. סשנים שזהות השירות של הארגון יוצרת נושאים ב-act.sub נושא agent:, ומשמיטים את act.email, ccr:account_id, account_email ו-account_uuid.
שתי טענות הדוא"ל אופציונליות גם בסשנים שנוצרו על ידי משתמש. Anthropic רושמת אותן ביצירת הסשן רק כאשר האישורים של בקשת היצירה נושאים דוא"ל, וסשן שנשלח מה-CLI יכול לחסר את שתיהן. קבעו זהות לפי act.sub או ccr:account_id ולא לפי דוא"ל. אסימונים יכולים גם לשאת טענות נוספות מעבר לטבלה הזו. התעלמו מטענות שאינכם מזהים. הטבלה למטה מפרטת את טענות אסימון הסשן שרלוונטיות לאימות.
| Claim | Type | Description |
|---|---|---|
iss | string | תמיד ccr. |
sub | string | ccr:session:<session_id>. |
aud | array of strings | תמיד מכיל את anthropic-api. בסשנים בסביבות באירוח עצמי המערך מכיל גם את מזהה הסביבה שלכם, כמו ccpool_.... אמתו את מזהה הסביבה, לא את anthropic-api. |
exp | number | תפוגה כחותמת זמן Unix. אורך חיים ברירת מחדל של ארבע שעות, מקסימום שמונה שעות. |
iat | number | זמן הנפקה כחותמת זמן Unix. |
jti | string | מזהה אסימון ייחודי. |
ccr:role | string | תמיד session_worker לאסימוני סשן. |
ccr:session_id | string | מזהה הסשן בצורה cse_.... אותו ערך כמו הסיומת של sub, ואותה מחרוזת כמו CLAUDE_CODE_REMOTE_SESSION_ID. כדי להתאים אותו ל-CLAUDE_RUNNER_SESSION_ID שהוקי מחזור חיים מקבלים, החליפו את הקידומת cse_ ב-session_. |
ccr:pool_id | string | מזהה הסביבה שלכם. אותו ערך שמופיע ב-aud. |
ccr:org_id | string | מזהה הארגון שלכם ב-Anthropic. |
ccr:account_id | string | מזהה חשבון Anthropic של המשתמש היוצר: הערך של act.sub בלי הקידומת user:, מזהה user_... מתויג. אותו ערך ש-CLAUDE_RUNNER_ACCOUNT_ID של הוק spawn-runner נושא וש---lock-to-account מקבל, כך שהשלושה מושווים כמחרוזות שוות. |
account_email | string | כפילות של act.email. נעדר בכל פעם ש-act.email נעדר. |
organization_uuid | string | ה-UUID של הארגון שלכם ב-Anthropic. |
account_uuid | string | ה-UUID של חשבון Anthropic של המשתמש היוצר. |
act | object | שרשרת האצלה לפי RFC 8693. ראו שרשרת ה-act. |
#שרשרת ה-act {#the-act-chain}
טענת act מתעדת את נתיב האצלה המלא מהמשתמש או מזהות השירות שיצרו את הסשן ועד לסביבה שהסוד שלה אישר את ה-runner, ואת הזהות שיצרה את הסוד הזה. היוצר הוא השחקן החיצוני ביותר, ולכן act.sub מזהה אותו ישירות.
| Path | Description |
|---|---|
act.sub | מזהה המשתמש של Anthropic של המשתמש היוצר, בצורה user:<id>, או agent:<id> כאשר זהות השירות של הארגון יצרה את הסשן. |
act.email | כתובת הדוא"ל של המשתמש היוצר, כאשר אחת נרשמה ביצירת הסשן. אל תדרשו אותה. קבעו מפתח לפי act.sub. |
act.attested_by | שמור לאישור של ספק הזהות במעלה הזרם עבור המשתמש היוצר. צפו שהוא ייעדר, ואל תלו בו. קבעו מפתח לפי act.sub. אם אתם צריכים כתובת, קראו את act.email כאשר היא קיימת. |
act.act | ה-runner שהפעיל את הסשן. act.act.sub הוא ccr:runner:<runner_id>. |
act.act.act | הסביבה. act.act.act.sub הוא ccr:pool:<pool_id>. |
act.act.act.act | הזהות שיצרה את סוד הסביבה שה-runner נרשם איתו. השרשרת מסתיימת כאן. |
#היקף אישורים נגזרים {#scope-derived-credentials}
אסימון הסשן מזהה את המשתמש או את זהות השירות שיצרו את הסשן, אבל אל תתייחסו אליו כשווה ערך לכניסה ישירה של היוצר הזה. האסימון יושב במשתנה סביבה בתוך הסשן, ולכן כל קוד ש-Claude מריץ, וכל כלי או שרת MCP שהסשן מפעיל, יכולים לקרוא אותו ולהציג אותו.
האימות גם לא מקוון: אסימון שמתאמת מול ה-JWKS נשאר תקף עד exp שלו, יהיה מה שיהיה שקרה לסשן מאז, ו-Anthropic לא מפרסמת הזנת ביטול לאסימוני סשן. הגבילו בהתאם כל דבר שנגזר מהאסימון.
כאשר השירות שלכם מחליף את האסימון באישורים פנימיים, הנפיקו אישורים שמוגבלים למה שסשן קידוד אחד אמור להגיע אליו:
- הגבלת יכולות: העניקו גישת קריאה וכתיבה למשאבים שהסשן צריך למשימות קידוד, לא ליכולות ניהול שהיוצר מחזיק במקום אחר.
- הגבלת אורך חיים: קבעו את האישורים הנגזרים ל-
expשל האסימון, או לקצר יותר. - ביקורת לפי הסשן: רשמו את
ccr:session_idואתjtiלצד זהות היוצר, כדי שתוכלו לעקוב אחרי פעולות חזרה לסשן מסוים.
#משתני סביבה קשורים
זהות היוצר מופיעה גם במשתני סביבה גלויים בשני משטחים שלעולם לא מאמתים את האסימון:
- הוק
spawn-runner, על ה-orchestrator: ההוק רץ לפני שקיים runner כלשהו לסשן בתור, ומקבל את זהות היוצר במשתנים כמוCLAUDE_RUNNER_ACCOUNT_EMAILו-CLAUDE_RUNNER_ACCOUNT_ID. ה-orchestrator קורא אותם מהזמנת העבודה, האסימון החתום לשימוש חד פעמי שמאשר הפעלת runner אחד, בלי לאמת בעצמו את חתימת הזמנת העבודה. הטענות מהימנות כי הזמנת העבודה מגיעה על החיבור של ה-orchestrator אל Anthropic, שסוד הסביבה מאמת. - סקריפטי מעטפת, בתוך הסשן: סקריפטי מעטפת מקבלים את
CCR_SESSION_ACCOUNT_EMAIL, הדוא"ל של היוצר שחולץ מראש מהאסימון בלי אימות חתימה. המשתנה מתאים לתיוג, כמו trailers של commit, לא להחלטות אימות.
השתמשו במשתנים הגלויים להחלטות בצד ה-orchestrator, כמו בחירת תמונת מכונה. השתמשו ב-CLAUDE_CODE_SESSION_ACCESS_TOKEN כאשר שירות בהמשך הדרך צריך הוכחה קריפטוגרפית עצמאית ולא אמון בסביבת ה-runner.
#מה הלאה
- סביבות באירוח עצמי: מודל הסביבה, ה-runner והסשן. התחלה מהירה ופריסה לייצור מכילים הגדרה ותפעול
- התאמת סשנים: סקריפטים עוטפים שצורכים את הטוקן, וההוק
spawn-runner - עיון: דגלי CLI, משתני סביבה ומדדים