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.
The 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 1.0.0:
- 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 — per scenario — 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, dependency-free module 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.
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 / Node
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 / Node 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 e44c684 — 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 mapping