— SDK v4 · verifyAgentToken · DELEGATED JWT VERIFICATION —
A guide for accepting delegated access tokens — the short-lived ES256 JWTs the AltsCodex delegation server (D-Server) issues to LLM agents acting on a user's behalf. Your app only verifies the token offline and enforces scope; no user is in the loop at request time. This is separate from the human login flow in the Backend SDK Guide. 에이전트가 사용자를 대신해 앱 API 를 호출할 때 받는 위임 액세스 토큰(D서버가 발급하는 단명 ES256 JWT)을 수용하는 가이드입니다. 앱은 이 토큰을 오프라인으로 검증하고 스코프만 확인하면 되며, 요청 시점에 사용자는 개입하지 않습니다. 사람 로그인 흐름을 다루는 Backend SDK 가이드 와는 별개입니다.
clientId/clientSecret to fetch slot info. Agent delegation is different: the user pre-authorizes an agent via a delegation on AltsCodex, the agent obtains a scoped short-lived token, and your app only verifies that token — you never hold the agent's key or the user's password. A pure resource server can construct the SDK with just authServerUrl.
사람 로그인(기존 SDK)은 앱의 clientId/clientSecret 으로 슬롯 정보를 조회합니다. 에이전트 위임은 다릅니다 — 사용자가 AltsCodex 에서 에이전트에게 위임을 미리 부여하면, 에이전트는 위임 범위 안의 단명 토큰을 받고, 앱은 그 토큰을 검증만 합니다. 앱은 에이전트의 키나 사용자의 비밀번호를 절대 보관하지 않습니다. 순수 리소스 서버는 authServerUrl 만으로 SDK 를 생성할 수 있습니다.
The user authorizes once (delegation). Every later agent request is judged by your app from the token alone — no user round-trip. 사용자는 한 번만 승인(위임)합니다. 이후 에이전트의 모든 요청은 앱이 토큰만으로 판단합니다 — 사용자 왕복 없음.
Verification is zero-dependency: it uses Node 18's built-in crypto (no jose, no jsonwebtoken) and requires Node 18+. Since v4, clientId/clientSecret/redirectUri are optional — a resource server that only calls verifyAgentToken() constructs with just authServerUrl.
검증은 의존성 0입니다 — Node 18 내장 crypto 만 사용하며(jose/jsonwebtoken 불필요) Node 18+ 가 필요합니다. v4 부터 clientId/clientSecret/redirectUri 는 선택이며, verifyAgentToken() 만 호출하는 리소스 서버는 authServerUrl 만으로 생성합니다.
npm install @altscodex/sdk # Node 18+
const AltsCodexBackend = require('@altscodex/sdk/backend');
// 리소스 서버(당신의 앱) — 로그인 자격증명 없이 authServerUrl 만으로 생성 (v4)
const rs = new AltsCodexBackend({
authServerUrl: 'https://api.altscodex.com',
// introspect(RFC 7662) 를 쓸 때만 필요 — 서버-투-서버 내부 토큰
internalToken: process.env.INTERNAL_TOKEN,
});
verifyAgentToken requires an audience (RFC 8707 resource indicator). It is the client identifier your app was registered under at the Developer Center — the same value the delegation named as aud. A mismatch throws audience_mismatch.
verifyAgentToken 은 audience(RFC 8707 resource indicator)를 필수로 받습니다. 이는 개발자 센터에 등록된 앱의 클라이언트 식별자이며, 위임이 aud 로 지정한 값과 같아야 합니다. 불일치 시 audience_mismatch 가 발생합니다.
await rs.verifyAgentToken(jwt, { audience, clockToleranceSec? }) —
Fetches & caches the D-Server JWKS (${authServerUrl}/.well-known/jwks.json; a kid miss triggers one forced refresh to absorb key rotation), verifies the ES256 signature, checks exp (and nbf if present), and matches aud. Then it parses the delegation claims and returns them.
D서버 JWKS(${authServerUrl}/.well-known/jwks.json)를 조회·캐시하고(kid 미스 시 1회 강제 갱신으로 키 로테이션 흡수), ES256 서명을 검증하고, exp(있으면 nbf 도)를 확인하고, aud 를 대조합니다. 이후 위임 클레임을 파싱해 반환합니다.
Returned AgentVerification:
반환값 AgentVerification:
| Field | Type | Description |
|---|---|---|
| user | string |
sub — the delegating user's identifier
sub — 위임한 사용자의 식별자
|
| agent | { sub, agent_type?, model?, provider? } |
act (RFC 8693) — the acting agent
act(RFC 8693) — 실제 행위 에이전트
|
| scope | string | Space-delimited granted scope of THIS token 이 토큰의 공백 구분 허용 스코프 |
| details | object[] |
authorization_details (RAR, RFC 9396) — limits / allow-lists
authorization_details(RAR, RFC 9396) — 한도 / 허용목록
|
| delegationId | string |
delegation_id — the delegation this token was issued under
delegation_id — 이 토큰이 발급된 위임 ID
|
| payload | object |
Raw JWT claims (iss, jti, exp, iat, ...)
원시 JWT 클레임(iss, jti, exp, iat, ...)
|
Full Express example — verify, gate on scope, double-check high-risk actions, then apply RAR limits: 전체 Express 예시 — 검증 → 스코프 게이트 → 고위험 이중검증 → RAR 한도 적용:
const express = require('express');
const AltsCodexBackend = require('@altscodex/sdk/backend');
const app = express();
app.use(express.json());
const rs = new AltsCodexBackend({
authServerUrl: process.env.ALTSCODEX_AUTH_SERVER_URL, // https://api.altscodex.com
internalToken: process.env.INTERNAL_TOKEN, // introspect 용
});
const AUDIENCE = 'app_marketplace'; // 이 앱의 등록 식별자 (토큰 aud 와 대조)
app.post('/market/bid', async (req, res) => {
// 1) Bearer 토큰 추출
const header = req.headers.authorization || '';
const token = header.startsWith('Bearer ') ? header.slice(7) : null;
if (!token) return res.status(401).json({ error: 'missing_token' });
// 2) 오프라인 검증 — 서명 · aud · exp 확인 후 위임 클레임 파싱
let v;
try {
v = await rs.verifyAgentToken(token, { audience: AUDIENCE });
} catch (err) {
// err.reason: invalid_signature / audience_mismatch / token_expired ...
return res.status(401).json({ error: err.reason || 'invalid_token' });
}
// 3) 스코프 게이트 — 위임 범위 밖이면 거부
if (!v.scope.split(' ').includes('market:bid')) {
return res.status(403).json({ error: 'insufficient_scope' });
}
// 4) 고위험(입찰/송금) 직전 introspect 로 실시간 활성 재확인 — 철회 즉시 반영
// verifyAgentToken 은 오프라인이라 발급 후의 철회를 볼 수 없다.
const info = await rs.introspect(token);
if (!info.active) return res.status(403).json({ error: 'delegation_revoked' });
// 5) RAR 한도 안에서만 수행 — 위임이 준 max_bid 상한과 카테고리를 읽는다
const rule = v.details.find((d) => d.type === 'altscodex:market_bid');
const maxBid = rule && rule.limits ? rule.limits.max_bid : undefined;
const categories = rule ? rule.categories : undefined;
if (req.body.amount > Number(maxBid)) {
return res.status(403).json({ error: 'bid_over_limit', maxBid });
}
// ... maxBid 이하로 입찰 수행 ...
res.json({ ok: true, user: v.user, agent: v.agent && v.agent.sub, maxBid, categories });
});
app.listen(3070, () => console.log('resource server on 3070'));
verifyAgentToken is offline and fast — call it on every request. introspect (RFC 7662) makes a live server-to-server call to the D-Server and can see a revocation that happened after issuance — add it immediately before an irreversible high-risk action (transfer, bid). introspect requires the constructor internalToken, sent as X-Internal-Token; never expose that token to clients.
verifyAgentToken 은 오프라인이라 빠릅니다 — 매 요청마다 호출하세요. introspect(RFC 7662)는 D서버로 실시간 서버-투-서버 호출을 하며 발급 후의 철회까지 반영합니다 — 되돌릴 수 없는 고위험 행위(송금·입찰) 직전에만 붙이세요. introspect 는 생성자 internalToken(X-Internal-Token 헤더로 전송)이 필요하며, 이 토큰을 클라이언트에 절대 노출하지 마세요.
err.reason)
실패 사유 (err.reason)
On failure verifyAgentToken throws an Error whose .reason is one of the codes below. Map them to HTTP status as shown.
실패 시 verifyAgentToken 은 아래 코드 중 하나를 .reason 에 담아 Error 를 던집니다. 표시된 HTTP 상태로 매핑하세요.
err.reason | Meaning | HTTP |
|---|---|---|
| malformed_token | Not a valid compact JWS (empty / wrong segment count / bad base64url)유효한 compact JWS 가 아님(빈 값 / 세그먼트 수 오류 / base64url 손상) | 401 |
| unsupported_alg | alg is not ES256 (alg-confusion / none defense)alg 가 ES256 이 아님(alg 혼동 / none 공격 방지) |
401 |
| unknown_key | No JWKS key matches the token's kid (even after forced refresh)토큰의 kid 에 맞는 JWKS 키 없음(강제 갱신 후에도) |
401 |
| invalid_signature | ES256 signature check failed — tampering suspectedES256 서명 검증 실패 — 변조 의심 | 401 |
| token_expired | exp is in the past (past clockToleranceSec)exp 가 과거임(clockToleranceSec 초과) |
401 |
| token_not_yet_valid | nbf is in the futurenbf 가 미래임(유효 시작 전) |
401 |
| audience_mismatch | Token aud does not include your audience토큰 aud 에 이 앱의 audience 가 없음 |
401 |
| missing_audience | You called verifyAgentToken without the required audience optionaudience 옵션 없이 verifyAgentToken 을 호출함(필수) |
500 |
A valid but under-scoped token is not a verification error — verifyAgentToken succeeds and you enforce scope yourself (return 403 when the required scope is absent).
유효하지만 스코프가 모자란 토큰은 검증 오류가 아닙니다 — verifyAgentToken 은 성공하며, 스코프 확인은 앱이 직접 합니다(필요 스코프가 없으면 403 반환).
MCP server (recommended) — expose your capabilities through the AltsCodex MCP server; token verification and scope gating happen at the MCP layer, so your app rarely touches raw JWTs. REST + verifyAgentToken (compatible) — an existing REST app verifies delegated tokens directly with this SDK, exactly as shown above.
MCP 서버(권장) — 기능을 AltsCodex MCP 서버로 노출하면 토큰 검증·스코프 게이트가 MCP 레이어에서 처리되어 앱이 원시 JWT 를 거의 다루지 않습니다. REST + verifyAgentToken(호환) — 기존 REST 앱은 위 예시처럼 이 SDK 로 위임 토큰을 직접 검증합니다.
audience — a token minted for app_marketplace is not valid at app_wallet, and forwarding it either fails with audience_mismatch or, worse, over-shares the delegation. If a downstream call is needed, that agent obtains its own delegated token for that audience.
받은 위임 토큰을 다른 다운스트림 서비스로 그대로 전달(passthrough)하지 마세요. 각 리소스 서버는 자신의 audience 로 검증해야 합니다 — app_marketplace 용으로 발급된 토큰은 app_wallet 에서 유효하지 않으며, 전달하면 audience_mismatch 로 실패하거나 더 나쁘게는 위임이 과도하게 공유됩니다. 다운스트림 호출이 필요하면 그 대상 audience 용 위임 토큰을 에이전트가 따로 받습니다.
The scope string and details (RAR) you read from a verified token come from this fixed vocabulary. Canonical source: SCOPES.md in the delegation server (if this doc and the code constant differ, the code wins).
검증된 토큰에서 읽는 scope 문자열과 details(RAR)는 아래 고정 어휘에서 옵니다. 정본은 위임 서버의 SCOPES.md 입니다(이 문서와 코드 상수가 다르면 코드가 정본).
resource:action)
스코프 — 10종 (리소스:행위)
| Scope | Class | Meaning |
|---|---|---|
| profile:read | read조회 | Agent / user profile에이전트·사용자 프로파일 조회 |
| slot:read | read조회 | Single slot detail특정 슬롯 상세 조회 |
| slot:list | read조회 | Slot list슬롯 목록 조회 |
| chat:read | read조회 | Read chat messages채팅 메시지 조회 |
| chat:send | action행위 | Send chat messages채팅 메시지 전송 |
| market:read | read조회 | Market prices / items마켓 시세·아이템 조회 |
| market:bid | action · high-risk행위·고위험 | Place a market bid마켓 입찰 |
| market:list_item | action행위 | List a market item마켓 아이템 등록 |
| wallet:read | read조회 | Wallet balance / history지갑 잔액·내역 조회 |
| wallet:transfer | action · high-risk행위·고위험 | Transfer from wallet지갑 송금 |
High-risk scopes (wallet:transfer, market:bid) are not enough on their own — they are further constrained by the RAR types below (amount / counterparty / quantity caps).
고위험 스코프(wallet:transfer, market:bid)는 그 자체만으로 부족하며, 아래 RAR 타입으로 금액·상대·수량 상한을 함께 제약합니다.
authorization_details types — 3 total (RFC 9396)
RAR authorization_details 타입 — 3종 (RFC 9396)
| Type | Use | Canonical constraint fields |
|---|---|---|
| altscodex:wallet_transfer | Transfer cap송금 상한 제약 | actions:["transfer"], limits:{per_tx, per_day, currency}, allow_to:[addr] |
| altscodex:market_bid | Bid cap입찰 상한 제약 | actions:["bid"], limits:{max_bid}, categories:[...] |
| altscodex:chat | Channel / rate cap채팅 상대·채널 제약 | actions:["send"], channels_allow:[...], rate:{per_hour} |
{ "type": "altscodex:market_bid", "actions": ["bid"],
"limits": { "max_bid": "30000" }, "categories": ["game_alt"] }
Amounts are strings (parse to number before comparing). Attenuation is monotonic — the token you receive is always a subset of the delegation: the D-Server rejects a token exchange that widens scope or raises a limit, so an over-scoped request never reaches your app. 금액은 문자열입니다(비교 전 숫자 파싱). 감쇠는 단조적입니다 — 받는 토큰은 항상 위임의 부분집합입니다. 스코프를 넓히거나 한도를 올리는 토큰 교환은 D서버가 거부하므로, 범위 밖 요청은 앱에 도달조차 하지 않습니다.