Core concept
API keys
Neuctra Authix issues two credentials. Which one you hold decides what you can reach — and the difference is the whole security model.
Two keys, two jobs
They are not tiers of the same key. They answer different questions.
| Publishable | Secret | |
|---|---|---|
| Prefix | pk_live_… | sk_live_… |
| Safe to ship | Yes | No — server only |
| Acts on | The signed-in user only | Any user in the app |
| Can do | Signup, login, verification, password reset, and read/write the signed-in user's own records | Everything, including listing every user and app-wide data |
| If leaked | Low impact — it can only do what a visitor could already do | Full account compromise — rotate immediately |
What a key looks like
Only the tail is secret. The prefix is a lookup handle and is safe to put in a log line.
pk_live_a1b2c3d4_9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a3928
│ │ │ └── 48 hex characters — the only secret part
│ │ └── lookup handle, safe to log
│ └── environment: live or test
└── type: pk (publishable) or sk (secret)At rest the server stores only a SHA-256 hash of the key, alongside the prefix and last four characters. Nobody — including you — can recover a key after it is issued, which is why the dashboard shows it exactly once.
Choosing one
1// Browser or mobile — safe to ship
2const authix = new NeuctraAuthix({
3 appId: APP_ID,
4 publishableKey: "pk_live_…",
5});
6
7// Server only — full account authority
8const authixAdmin = new NeuctraAuthix({
9 appId: APP_ID,
10 secretKey: process.env.AUTHIX_SECRET_KEY,
11});The rule is not about language, it is about reachability: if the code ships to a device you do not control, it gets a publishable key. A React bundle, a Flutter binary and a mobile web page are all in that category. A Node service, a Python API and a Next.js Server Action are not.
Why a publishable key is safe to expose
Not because it is unimportant, but because the server does not trust it with anything dangerous.
Requests are pinned to the session
When a request arrives with a publishable key, the server resolves the end-user session from the cookie and pins the target to that user. The user id in the URL or body is ignored.
So an attacker holding your publishable key can do exactly what any visitor to your app can do: sign up, sign in, and act on their own account. They cannot read anyone else's records by editing an id, because that id is never consulted.
Endpoints that read across users — listing your user table, app-wide data, cross-user search — reject a publishable key outright rather than returning a filtered subset. Deny by default, not filter by default.
Misuse fails before the network
Every SDK parses the key at construction, so the two swaps that actually happen in practice are caught immediately.
new NeuctraAuthix({ appId, publishableKey: "sk_live_…" });
// Error: a secret key (sk_…) was passed as 'publishableKey'.This matters most in the direction that is dangerous. Shipping a secret key inside an app is not a bug you find in testing — everything works, and the exposure is silent. Failing at construction turns it into an error you cannot miss.
When the key is wrong for the endpoint
A 403 with a machine-readable code, and a hint that names the fix.
{
"success": false,
"message": "This endpoint requires a secret key.",
"code": "INSUFFICIENT_SCOPE",
"hint": "Publishable keys (pk_…) are browser-safe and limited to end-user authentication and the signed-in user's own data. Use a secret key (sk_…) from your server for this call."
}Each SDK raises this as a distinct type — InsufficientScopeError — so you can handle it separately from a wrong password. In practice it almost always means the call belongs on your server.
Rotating a key
- Create the replacement in the dashboard first.
- Deploy it everywhere the old key is used. Both keys work during this window, so there is no outage.
- Revoke the old key. Revocation takes effect on the next request — there is no cache to wait out.
Rotate a secret key immediately if
- It was committed to a repository, even a private one.
- It appeared in a client bundle — including via a
NEXT_PUBLIC_,VITE_or--dart-definevariable. - It was pasted into a log, a ticket, or a chat message.
Related