Secrets and authentication
Two different kinds of secret reach a Worker, and they are managed by different commands:
ATOMS_SHARED_SECRETauthenticates your application and the Worker to each other. There is exactly one, both sides hold the same value, and Atom code can never read it.- Application secrets — an API key your Atom needs — are set with
secrets:setand read back through$this->config().
The shared secret
Section titled “The shared secret”ATOMS_SHARED_SECRET is a base64 value that decodes to 32 random bytes.
Generate one, save it in your secret manager, and configure that same value as
ATOMS_SHARED_SECRET in your application and CI environment. Then supply it to
the Worker:
openssl rand -base64 32printf '%s' "$ATOMS_SHARED_SECRET" | \ vendor/bin/atoms shared-secret:set --env productionRun this after the first deployment, because the Worker must exist before a
secret can be set on it. Until then, every route but /healthz returns a
configuration error — a Worker with no valid secret fails closed rather than
serving unauthenticated traffic.
The value is never transmitted. Both sides derive purpose-specific keys from it:
the bearer token your application sends, the signature on callbacks the Worker
POSTs back, and the short-lived tickets browsers use to open WebSockets. For the
derivations and test vectors, see
docs/shared-secret.md,
which is the normative record.
Bearer auth postures
Section titled “Bearer auth postures”The Worker variable ATOMS_BEARER_AUTH controls whether the Worker checks the
Authorization bearer your application sends automatically. Leave it at the
default, required; set it to disabled only when an authenticating proxy such
as Cloudflare Access already sits in front of the Worker.
ATOMS_SHARED_SECRET stays mandatory in either posture, and browser connections
are unaffected — they authenticate with a short-lived
ticket either way.
Calling a protected route by hand
Section titled “Calling a protected route by hand”atoms token prints the bearer derived from the shared secret, so you never
have to paste the secret itself into a header. This example calls the join
method from the overview:
curl -H "Authorization: Bearer $(vendor/bin/atoms token)" \ -H 'Content-Type: application/json' \ --data '{"args":["ada"]}' \ https://your-worker.example.workers.dev/invoke/GameRoom/room-42/joinatoms token needs the secret itself, and reads it from
ATOMS_SHARED_SECRET in the environment the command was started with — or, for
local runs, from the Worker project’s .dev.vars. It takes no --env: the
bearer is derived from whichever secret it finds, and a secret is not
per-environment as far as this command is concerned — you select the
environment by supplying that environment’s secret. It is deliberately the one
value that does not come from
.env.atoms.<environment>: that
file is for deployment configuration, and this is the key both halves
authenticate with. Read it per command rather than leaving a production secret
in your shell:
BEARER=$(ATOMS_SHARED_SECRET=$(op read op://vault/atoms/shared-secret) \ vendor/bin/atoms token)Application secrets
Section titled “Application secrets”Use secrets:set for values your Atom reads through $this->config():
printf '%s' "$PAYMENTS_API_KEY" | \ vendor/bin/atoms secrets:set PAYMENTS_API_KEY --env productionThis stores the Worker secret ATOMS_CONFIG_PAYMENTS_API_KEY — the name is
uppercased and prefixed — readable through $this->config('PAYMENTS_API_KEY').
The prefix is what makes a Worker secret visible to Atom code at all, so
ATOMS_SHARED_SECRET and its rotation partner are not readable and cannot be
made readable: secrets:set refuses those names outright.
secrets:list shows which secrets an Atom can read and which it cannot.
A changed value is not retroactive: an Atom already resident in memory keeps the value its isolate started with.
Rotate the shared secret
Section titled “Rotate the shared secret”Senders use ATOMS_SHARED_SECRET; verifiers accept that value and
ATOMS_SHARED_SECRET_PREVIOUS. Prepare the overlap before replacing the current
value. Starting with the same old secret on the application and Worker:
- Configure every application instance with the old value as current and
the new value as
ATOMS_SHARED_SECRET_PREVIOUS. Reload the instances so all can verify callbacks signed with either value. - Set the Worker’s
ATOMS_SHARED_SECRET_PREVIOUSto the old value withshared-secret:set --previous --force. Let that change propagate before setting its current secret to the new value withshared-secret:set --force. - Configure the application with the new value as current and the old value as previous. Reload all application instances. Both sides now send with the new value and accept both.
- After both deployments have updated and old tickets have expired, run
shared-secret:unseton the Worker and remove the previous value from the application. Reload the application again.
Pass --env production to these commands. They all reach Cloudflare through
Wrangler, so they need credentials and resolve them exactly as deploy does —
see Authenticate with
Cloudflare.
shared-secret:set reads the value from stdin. Store both values in your secret manager during the rotation. Secret
changes propagate over time; verify application calls, callbacks, and browser
connections between stages.
Steps 1 and 3 are entirely yours — nothing in Atoms can reach your application’s secret store, so put them in your own deployment runbook. To drive a rotation from CI, see Deploy from GitHub Actions.