The refresh token,
finally specified.
Rotation, reuse detection and family revocation - the model major identity providers run internally - as one precise specification with reference implementations in ten languages. Drop it into the backend you already have.
See the policy at work
Inspect a token like on jwt.io - except there is nothing to decode. Then play attacker and watch reuse detection burn the family.
Parsed entirely in this browser tab. No network request, no analytics event, no storage — the text above never leaves your device. Even so, treat a real refresh token as a password: do not paste a production token into any web page, including this one. The box is pre-filled with a randomly generated fake.
- prefix
- nbl
- kid
- k1
- selector
- GT46tR830L85uO600zy4Xw
- verifier
- it59P7_e2K…
Unlike a JWT, this token carries no payload, no algorithm field and no signature to confuse. The selector points at one server-side record; the verifier proves possession of it.
The server never stores this string. It keeps a keyed hash — HMAC-SHA-256 of the verifier under a pepper — and the pepper lives in your environment or KMS, never in the database. The hash is one-way: it is not encryption and there is no key that turns it back into a token. A stolen database dump is inert, because the rows contain no credential and no way to reconstruct one.
Two parties, each holding their own copy of a token. Every refresh looks up the record for the token that party actually presents — which is what makes a replay a replay.
- No token family yet. Press Run the theft scenario to watch reuse detection burn one, or drive it step by step.
Ten languages, one behavior
Every port passes the shared test vectors and the full behavioral suite. Pick your language, copy the quickstart, keep the exact same semantics.
TypeScript
Same engine, same store contract, same error codes as every other port.
An agent skill is a short instruction file an AI coding assistant loads before it writes code. NEBULA ships ten of them, one per implementation, because the call sequence and the traps are language-shaped. This one teaches an assistant to integrate NEBULA in TypeScript the way the specification says to — and it carries the five rules that are easiest to get wrong:
- [N-35]
CONFLICTis transient — retry the refresh exactly once. - [N-30]
reuseGraceSecondsdefaults to0. Raising it buys retry tolerance and costs detectability: inside the window a thief is served a valid token and noREUSE_DETECTEDis raised. - [N-21] The in-memory store is not for production: per-process, lost on restart, blind behind two instances.
- [N-39] Failures carry
userIdandfamilyIdwhenever a record resolved — the security event needs no second lookup. - [N-46] Never log a token, the verifier, the pepper or the raw device id. The selector may be logged as a correlation id.
Same command for all ten; only the --skill argument changes. --skill '*' takes the lot, -l lists them without installing, -a picks the agent (claude-code, codex, …). It installs into the current project — .claude/skills/ for Claude Code, committed and shared with your team; -g installs into ~/.claude/skills/ for you alone.
The marketplace is added once. After that every language is one /plugin install away, and /plugin marketplace update re-syncs the catalogue.
No network for npx? Offline and vendored routes
All ten are in that one archive — nebula-token-skills.zip. And if the package is already installed you need neither: every published artefact carries the same nebula-token-typescript/ directory, ready to copy into a skills directory as-is. Where that directory sits inside each ecosystem's artefact — the jar and the Composer package do not put it in the same place as the rest — is in the repository's skills index, which is where that table is maintained.
How it works
The full algorithm is ten precisely ordered checks - this is the shape of it.
Login
issue() mints generation 0 of a new token family. The server stores only an HMAC of the secret - a database dump is inert.
Refresh
Every use rotates: the presented token dies, a successor is born. A stolen token is only useful until its owner refreshes.
Replay
An already-rotated token presented again means two parties hold one credential. The whole family is revoked instantly - attacker and stale sessions both cut off.
Expiry
Two clocks: an absolute deadline fixed at login that nothing extends, and a sliding idle window renewed on each refresh.
Key rotation
Peppers are keyed by kid. Rotate them live - old tokens keep verifying, new tokens use the new key, nobody gets logged out.
Normative, citable, and generated
One document decides what NEBULA is; five more say how to run it. Every page below is rendered straight from the specification repository at v1.0.2 — not transcribed, so it cannot drift — and every requirement has a permanent anchor you can cite, like [N-30].
Specification
The single normative definition of NEBULA: token format, store contract, the ordered refresh algorithm, reuse handling and error codes, each numbered [N-*].
NEBULA Specification, Version 1Integration guide
Wiring NEBULA into an HTTP API: engine setup, the cookie attribute by attribute, the three endpoints, device binding, and the error codes at the boundary.
Integrating NEBULA into an HTTP APIProduction store
Implementing a production store: the schema, the six methods as SQL, how a lost compare-and-set becomes CONFLICT, retention, transactions and isolation.
Implementing a production storeThreat model
Assets, trust boundaries, adversary model, threats and mitigations, the quantum adversary, explicit non-goals, and the security events worth alerting on.
NEBULA Threat ModelOperations
Running NEBULA: metrics, what to alarm on, planned and emergency pepper rotation, the garbage-collection job, capacity, backups and incident response.
Operating NEBULACompliance mapping
Mapping NEBULA to OWASP ASVS V3, NIST SP 800-63B, PCI DSS and GDPR — and being explicit about what it does not address at all.
Compliance mappingThe token is not the defense.
The policy is.
A stolen refresh token that keeps working for days is the real threat - and no amount of encryption inside the token fixes it. Server-side policy does.
Rotation & reuse detection
Every refresh invalidates the presented token. Replaying a dead one burns the whole session family - the attacker is cut off and the theft surfaces as a security event you can alert on.
Hashed at rest, keyed by kid
The server stores only HMAC-SHA-256(pepper, verifier). Peppers live in your env or KMS and rotate with zero downtime. A full database dump yields nothing replayable. And with no public-key crypto anywhere, Shor's algorithm has no target: the token layer is quantum-resistant by construction, with a ~128-bit margin even against Grover.
When to use NEBULA — and when not to
Use NEBULA when you build first-party authentication in your own backend and want refresh-token handling that matches what managed identity providers do internally: no per-user pricing, no vendor lock-in, session data in your own database (relevant for GDPR / data residency), identical behavior across every language in your stack.
Do not use NEBULA when: you already run a managed IdP (Auth0, Okta, Cognito — use its built-in rotation); you need full OAuth 2.0 / OIDC with third-party clients, scopes, and consent screens (you need an authorization server such as Keycloak or Ory, not a token library); you operate under FAPI 2.0 / open-banking requirements (you additionally need cryptographic sender-constraining — DPoP or mTLS — at the protocol layer; NEBULA's device binding is application-level); or your stack is a single language and its authorization-server framework already does this (OpenIddict, Spring Authorization Server, Fosite, Authlib — each is better integrated with its ecosystem than NEBULA will ever be, and the ten-language conformance guarantee is worth nothing to you).
FAQ
Why does it need a database?
Because instant revocation and reuse detection require server-side state — a stateless token cannot be revoked before it expires, which is exactly why RFC 9700 moved the industry away from stateless refresh tokens. NEBULA doesn't pick your database: it defines a six-method contract you implement over whatever you already run. Cost: one indexed lookup per refresh.
Why not a JWT as refresh token?
A JWT proves who signed it, but cannot be un-signed: revocation requires a server-side denylist — at which point you have a database anyway, plus a signature scheme you no longer need. Claims belong in the short-lived access token; the refresh token is a credential, not an envelope.
Why not self-host Ory, Keycloak or Supabase Auth?
If you need what those are — an identity provider that owns login, consent and the OAuth 2.0 protocol surface — run one, and take its rotation with it. The question only gets interesting when the refresh-token mechanism is all you need. Those engines are multi-language in the sense that any language can call their HTTP API: there is one implementation, behind a network hop, that you deploy, patch, monitor and keep available, and it wants to own your user table. NEBULA is multi-language in a different sense — ten in-process implementations that agree byte-for-byte, pinned by shared vectors. A refresh is an HMAC and one compare-and-set write against the database you already run: no service in the path, nothing extra that can be down, and session data that stays in your own schema. What NEBULA competes with is not an identity provider; it is the rotation logic you would otherwise write by hand — where the usual defect is the concurrent refresh: two parallel requests presenting the same token both observe it as active, both mint a successor, and the family forks into two independently valid lineages — which defeats reuse detection entirely. That is not an exotic race; it is a mobile client retrying, or two browser tabs refreshing together. Here it is a compare-and-set with a specified losing branch ([N-34] step 5, returning a retryable CONFLICT per [N-35]) rather than something left to the reader.
What is genuinely new here?
Not the cryptography — deliberately. What did not exist before is a precise, testable, language-independent specification of the RFC 9700 rotation model: exact check order, exact reuse/grace semantics, shared conformance vectors, and ten implementations held to them by one executable suite rather than by ten hand-written ones.
Maturity, stated plainly
A library on an authentication path should tell you what it has and has not been through. NEBULA's position, as of the 1.0 line:
- No independent security audit. None has been performed.
SECURITY.mdkeeps the full list of what is missing — no formal verification of the rotation state machine, no fuzzing beyond the published vectors. - No production track record. These implementations are new. Nobody can yet tell you how they behave at scale, because nobody has run them at scale.
- What is real is the conformance. Behaviour is not asserted in prose and hoped for in code: it is pinned by 46 test vectors and 38 executable behavioural scenarios that every implementation runs on every CI run, in all ten languages, with a runner that must assert the published case counts and fail on an empty section ([N-48]). Two implementations that pass agree at every point those artefacts observe: the accept/reject decision for the token strings the parsing vectors cover, the byte-level output of both keyed hashes, and — for each scenario, over whichever of them it asserts — the outcome code, the generation, the family and expiry relations, and the resulting multiset of record statuses in the store. That is a bounded claim rather than a universal one, and it is deliberately the one this project makes: the vectors do not observe timing, log output or store call ordering, and no differential harness yet drives all ten from one random transcript. It is also checkable — checkable by you, not just by our CI:
node scripts/docker-test.mjsruns all ten suites in official images pinned by digest at the declared version floors, with nothing installed but Docker (docker/README.md). - The surface is small on purpose. Each implementation is one small package — dependency-free in eight of the ten — meant to be read in a sitting. The most useful thing you can do before adopting it is read it.
What is frozen and what may still move is spelled out in COMPATIBILITY.md; how a third-party port may claim conformance ([N-53]) is in GOVERNANCE.md.