Your API shouldn't own its users
Every API ends up rebuilding user tables, password hashing, lockout and GDPR erasure. Nod is a self-hosted auth backend your APIs call instead. Here is what it is, and the decisions behind it.
Count the user tables in your company.
Not the one in the main product. All of them. The internal billing tool, the partner API, the reporting service someone built over a weekend that ended up in production. Each has a users table, a password column, a login endpoint and, if you are lucky, some kind of lockout.
Each one was written by a different person in a different year. One hashes with bcrypt at cost 10, another with whatever the framework defaulted to in 2019. One locks accounts after five failed attempts and the other one never does. When a customer asks to have their data erased, someone opens a ticket for each of those services and hopes the list is complete.
Nobody decided to build it this way. It happens because user management looks small at the start: a table, a hash, a session. The hard parts show up later: rotating tokens, revoking every session after a password change, rate limits, MFA, audit trails, and answering a data-protection request without breaking your own history.
We built Nod so that work gets done once, properly, in one place.
What Nod is
Nod is a self-hosted authentication backend for your APIs. Instead of every service owning its users, each service registers with Nod as a tenant and calls Nod to manage its end users:
- registration and login
- password changes and self-service password reset
- account lockout with exponential backoff
- TOTP multi-factor authentication with single-use recovery codes
- refresh tokens that are rotated on every use and can be revoked one by one
- “log out everywhere” after a password change
- the right to be forgotten
- an append-only audit log of everything Nod does
The interaction is machine to machine. Your API authenticates to Nod with a tenant API key, then makes plain HTTP calls under /v1/.... There is no widget to embed, no redirect dance, and no hosted login page owned by someone else. Your service keeps its own UX and stops owning credentials.
Because it is one service, it is also one place to audit. Every action Nod takes is recorded against a stable subject ID, and you can read that log back through the API.
Three decisions that define Nod
A good way to judge an auth system is to look at what it refuses to make configurable. These three are not settings in Nod.
1. Paseto, not JWT
Nod issues Paseto v4 tokens (v4.public, Ed25519-signed). It never issues JWTs.
JWT’s problems are well documented: an alg header the attacker controls, none as a valid algorithm, and libraries that have confused symmetric and asymmetric keys more than once. You can use JWT safely, but only if every verifier in your company gets every detail right.
Paseto removes those choices. A v4.public token has exactly one algorithm, picked by the version, so there is nothing to negotiate and nothing to downgrade. Your services verify Nod’s tokens with its public key and nothing else.
Access tokens also carry a token version. When a user changes their password or asks to log out everywhere, Nod increments it, and every token issued before that stops being accepted. That works even though the tokens are stateless.
2. Argon2id, and only Argon2id
Every password hash Nod writes is Argon2id (m=64MB, t=3, p=4). There is no setting to switch to bcrypt or scrypt and no per-tenant override.
That raises an obvious objection: “I already have 40,000 users with bcrypt hashes. I’m not making them all reset their password.”
You don’t have to. Nod’s user import accepts existing bcrypt and Argon2 hashes, within sane parameter limits. They are stored in a separate legacy table and verified once, at the user’s next login. If that login succeeds, Nod immediately replaces the hash with a fresh Argon2id one and deletes the legacy hash. Legacy hashes that are never used expire (after 90 days by default), and those users fall back to setting a new password.
So you can migrate without forcing a mass reset, and Nod still never creates a weak hash.
3. The right to be forgotten without wrecking your audit log
GDPR’s right to erasure and a trustworthy audit log seem to conflict. If you delete the user row, every audit entry that pointed to it now points to nothing, and your history is broken. If you keep the row, you haven’t erased anything.
Nod handles this with anonymisation in place:
- The user’s ID stays, so audit references remain valid.
- The email becomes
deleted-{uuid}@anonymized.invalid. - Password fields are cleared and every other personal field is set to null.
The audit log references users only by their stable ID. It never stores an email, a name or any other personal data in a log entry, and entries are append-only. Once a user is anonymized, the log still says what happened without saying who it happened to. Erasure takes effect immediately, with no grace-period batch job.
What Nod deliberately isn’t
Being clear about scope matters as much as the feature list.
- Not a third-party identity provider. Nod authenticates your own services and their users. It is not “Log in with Nod,” and it doesn’t federate over OIDC or SAML. We harden first-party flows (admin-approved tenant scopes and consent gating have already shipped), but we don’t hand out delegated access to third parties.
- Not hosted. Nod runs on your infrastructure, next to your Postgres and Redis. Your users’ credentials never leave your network.
Where it stands
Nod is in closed beta. We onboard each early tenant by hand, because we would rather work closely with a few teams than open the doors early.
If you have more than one service with its own users table and would like it to be one, we’d like to hear from you.
Ready to stop building auth yourself?