Skip to main content
Self-hosted environments are in public beta on Team and Enterprise plans; an Owner or admin enables them by turning on Allow self-hosted environments on the Cloud environments admin page. This page covers session identity verification; see the quickstart for setup and Deploy to production for the fleet recipes.
A self-hosted environment lets Claude Code on the web sessions run on infrastructure you operate instead of on Anthropic’s. Because the session runs inside your network, Claude can call your internal services directly. Those services need a way to confirm that a request really came from a Claude Code session in your environment, and to identify which user created that session. Every session in a self-hosted environment receives a signed JSON Web Token (JWT) in the CLAUDE_CODE_SESSION_ACCESS_TOKEN environment variable. A session presents the token like any bearer credential; for example, a script Claude runs can call your service with curl -H "Authorization: Bearer $CLAUDE_CODE_SESSION_ACCESS_TOKEN". Anthropic signs the token and publishes the verification keys at a public JWKS endpoint. Your services fetch those keys, verify the signature, and read the claims to decide what access to grant.

The session token

Before you write verification code, know what the token establishes and the shape your JWT library will see.

What the token proves

A valid token establishes some facts and deliberately not others:
  • Proves: Anthropic issued the token for a specific session in a specific environment, and how the session was created: by a user in your organization, or with an organization service key
  • Doesn’t prove: which process on the runner host presents it. The token sits in an environment variable inside the session, so any code Claude runs, and any tool or MCP server the session starts, can read and present it.
Two consequences for your services:
  • Verify the aud claim against your environment ID, the ccpool_... value shown with your environment on the Cloud environments admin page, to reject tokens issued to any other organization’s environment.
  • Scope credentials you derive from the token to what a single coding session should be able to do, not to everything the creating user can do. See Scope derived credentials.

Token format

The value of CLAUDE_CODE_SESSION_ACCESS_TOKEN has an sk-ant-cc- prefix followed by a standard three-part JWT:
Strip the prefix before passing the value to a JWT library. Tokens issued to Anthropic-hosted cloud sessions carry an sk-ant-si- prefix instead and are signed by a different key set, so reject any value that doesn’t start with sk-ant-cc-. The signature algorithm is ES256, which is ECDSA on the P-256 curve with SHA-256. The token header carries a kid that identifies which key in the JWKS signed it.

Verify the token

Verification runs in one of two places. Services on your network verify the token cryptographically against Anthropic’s published keys, and wrapper scripts inside the session can use the runner binary’s built-in decoder instead.

Verify the token from your service

Anthropic publishes the verification keys at a public, unauthenticated endpoint:
The response is a standard JSON Web Key Set. Anthropic rotates the signing keys periodically, and keys from before a rotation remain in the set long enough that tokens they signed continue to verify, so don’t pin a single key. The endpoint sets Cache-Control: public, max-age=300, so caching the key set and refetching every five minutes is safe. Verify each incoming token against these checks:
1

Check the prefix

Reject the value if it doesn’t start with sk-ant-cc-, then remove that prefix. The remainder is a standard compact JWT.
2

Verify the signature

Fetch the JWKS, select the key whose kid matches the token header, and verify the ES256 signature. Reject tokens whose alg header is not ES256. If a token arrives with a kid that isn’t in your cached key set, refetch the JWKS once before rejecting it: after a rotation, new tokens are signed with a key your cached set doesn’t have yet.
3

Verify the issuer

Reject the token if iss is not exactly ccr.
4

Verify the audience against your environment

The aud claim is an array. Reject the token unless it contains your environment ID, which has the form ccpool_.... The environment ID is shown in your environment’s detail dialog on the Cloud environments admin page, and appears as the ccr:pool_id claim in any of the environment’s session tokens. This check is what scopes the token to your environment and rejects tokens issued to other organizations.
5

Verify the role

Reject the token if ccr:role is not exactly session_worker. Other tokens issued for self-hosted environments, such as environment secrets, runner tokens, and work orders, are signed by the same key set but carry different roles.
6

Verify expiry

Reject the token if exp is in the past. Anthropic issues session tokens with a four-hour lifetime by default and a maximum of eight hours. The runner refreshes the token before expiry and pushes the new value to the session, so subprocesses that Claude starts after a refresh inherit it. One session can therefore present several distinct valid tokens to your service over its lifetime.
7

Read the identity

The creating user’s identity is in the act claim: act.sub is their Anthropic user ID in the prefixed form user:<id>, and act.email, when the creating surface recorded one, is their email address. Sessions created with an organization service key carry no user identity, so treat a session as user-created only when act.sub carries the user: prefix, rather than testing whether identity claims are absent. See the claims reference for the full structure and the flat duplicate claims.
The checks map directly onto standard JWT libraries. The examples below implement the full sequence in Node.js with jose, which handles JWKS fetching, caching, and kid selection, and in Python with PyJWT and its built-in JWKS client.

Verify the token inside the session

Wrapper scripts run inside the session, before Claude starts. Instead of calling a JWT library, they can run the runner binary’s self-hosted-runner decode-token subcommand. The subcommand reads the token from a positional argument, from CLAUDE_CODE_SESSION_ACCESS_TOKEN, or from piped stdin, in that order, then strips the prefix, verifies the signature against the JWKS endpoint, checks expiry, and prints the claims as JSON. The subcommand performs the signature and expiry checks only; it doesn’t check iss, aud, or ccr:role. When your wrapper’s auth decision depends on those claims, read them from the printed JSON and compare them explicitly. This command extracts the creator identity, preferring the SSO provider’s subject, then the email address, then the always-present Anthropic user ID:
Wrappers receive the absolute path to the runner’s own binary in CLAUDE_RUNNER_CLAUDE_BIN; use that path rather than a PATH-resolved claude so the decode runs on the same binary the runner itself uses. Use jq -re rather than jq -r so a missing claim causes a non-zero exit. With -r alone, a missing claim prints the literal string null and exits zero, which silently passes a bad value downstream. Pass --no-verify to decode-token only for offline inspection where the JWKS endpoint is unreachable.

Claims reference

The table below lists the session token claims relevant to verification. Read identity from the ccr:* namespace and the act chain; the flat account_email, organization_uuid, and account_uuid claims are backward-compatibility duplicates that may be removed. Sessions created with an organization service key omit act.email, ccr:account_id, account_email, and account_uuid. The two email claims are optional for user-created sessions too: Anthropic records them at session creation only when the creating request’s credentials carry an email, and a session dispatched from the CLI can lack both, so key identity on act.sub or ccr:account_id rather than on email. Tokens can also carry additional claims beyond this table; ignore claims you don’t recognize.

The act chain

The act claim records the full delegation path from the user who created the session down to the environment whose secret admitted the runner, and the identity that created that secret. The creating user is the outermost actor, so act.sub identifies them directly.

Scope derived credentials

The session token identifies the creating user, but don’t treat it as equivalent to that user logging in directly. The token sits in an environment variable inside the session, so any code Claude runs, and any tool or MCP server the session starts, can read and present it. Verification is also offline: a token that verifies against the JWKS stays valid until its exp, whatever has happened to the session since, and Anthropic doesn’t publish a revocation feed for session tokens. Bound anything you derive from the token accordingly. When your service exchanges the token for internal credentials, issue credentials scoped to what one coding session should reach:
  • Limit capabilities: grant read and write access to the resources the session needs for coding tasks, not administrative capabilities the user holds elsewhere.
  • Limit lifetime: bound derived credentials to the token’s exp, or shorter.
  • Audit as the session: record the ccr:session_id and jti alongside the user identity so you can trace actions back to a specific session.
The creator identity also appears in plain environment variables on two surfaces that never verify the token:
  • The spawn-runner hook, on the orchestrator: the hook runs before any runner exists for a queued session and receives the creator identity in variables such as CLAUDE_RUNNER_ACCOUNT_EMAIL and CLAUDE_RUNNER_ACCOUNT_ID. The orchestrator reads them from the work order, the signed single-use token that authorizes spawning one runner, without verifying the work order’s signature itself; the claims are trusted because the work order arrives over the orchestrator’s connection to Anthropic, which the environment secret authenticates.
  • Wrapper scripts, inside the session: wrappers receive CCR_SESSION_ACCOUNT_EMAIL, the creator’s email pre-extracted from the token without signature verification. The variable is suitable for labelling, such as commit trailers, not for auth decisions.
Use the plain variables for orchestrator-side decisions such as selecting a machine image. Use CLAUDE_CODE_SESSION_ACCESS_TOKEN when a downstream service needs independent cryptographic proof rather than trusting the runner’s environment.

What’s next