net_backend

API reference

Accounts & authentication

Tokens#

A login answers an AuthSession: the account and a token pair.

json
{
  "account": {
    "id": 42,
    "email": "player@example.com",
    "email_verified": false,
    "display_name": "Player One",
    "roles": [],
    "identities": [],
    "created_at": 1790000000000
  },
  "tokens": {
    "token_type": "Bearer",
    "access_token": "nbsa_…",
    "access_expires_at": 1790003600000,
    "refresh_token": "nbsr_…",
    "refresh_expires_at": 1792592000000
  }
}
Token Lifetime (default) Use
access token 1 hour (access_expires_at) Authorization: Bearer <access token> on HTTP requests and the WebSocket handshake, or the WebSocket auth message
refresh token 30 days (refresh_expires_at; every refresh starts a new one) POST /v1/auth/refresh only
  • Tokens are opaque strings (today nbsa_ / nbsr_ plus 64 hex characters). Store and send them unchanged; never parse them. Use the *_expires_at fields, not the token text, for timing.
  • Refresh before expiry: when less than 60 seconds of the access token are left, refresh.
  • Rotation: every refresh token works once and is replaced by the answer's new pair. Presenting the same refresh token again within 30 seconds of its first use (an HTTP retry, two tabs refreshing at once) answers the same new pair. Presenting it again later revokes the whole session (refresh_token_reused: the token was probably stolen), and the player must log in again. So: refresh single-flight, store the new pair before using it, keep the old pair until the new one is stored, and share tokens between tabs (see the checklist).
  • Sessions: one per login. Logout revokes one session or all of them; a password change revokes the other sessions; a password reset or a ban revokes all. Revocation takes effect on the next request; open WebSockets of a revoked session are closed (4001, or 4003 for a ban).
  • Expired or revoked tokens: an expired access token answers 401 token_expired (refresh and retry); an unknown, malformed or revoked one 401 unauthorized; a banned account's token or refresh 403 banned. Routes that do not need a caller (register, login, refresh, logout, forgot / reset, verify) ignore a stale Authorization header, so a client that always sends its last token still works there. POST /v1/auth/steam is the exception: there a Bearer token that is invalid or expired is refused.

Register, log in, refresh, log out#

Step Request Answer
register POST /v1/auth/register {"email","password","display_name"?} AuthSession; a verification mail is sent
log in POST /v1/auth/login {"email","password"} AuthSession
refresh POST /v1/auth/refresh {"refresh_token"} TokenPair
log out this session POST /v1/auth/logout {} with the Bearer token, or {"refresh_token":"…"} when the access token expired {}
log out everywhere POST /v1/auth/logout {"everywhere":true} (+ Bearer or refresh_token) {}

Login answers the same 401 invalid_credentials for an unknown address and a wrong password. A server may require a verified address for password logins (403 email_not_verified) or close registration (403 forbidden).

Email verification and password reset (website pages)#

Mails contain a one-time token, or a link the operator configured with the token in it, for example https://your-site.example/verify?token=… and https://your-site.example/reset?token=…. A website implements those two pages:

Page What it does
/verify?token=… POST /v1/auth/email/verify {"token":"<token from the URL>"} → {}, or 400 invalid_token (unknown, used or expired: offer "send again", which is POST /v1/auth/email/resend with the player's Bearer token)
/reset?token=… a form for the new password, then POST /v1/auth/password/reset {"token":"…","new_password":"…"} → {}; every session of the account is revoked, so log in afterwards
"forgot password" form POST /v1/auth/password/forgot {"email":"…"} → always {} (whether or not the address has an account)

Verification tokens expire after 24 hours, reset tokens after 1 hour; both are single-use. A reset also unlinks login providers (Steam) by default.

Steam#

A game gets a ticket with Steamworks' GetAuthTicketForWebApi(identity) and posts it hex-encoded with the identity string: POST /v1/auth/steam {"ticket_hex":"…","identity":"…"} → AuthSession. The server checks the ticket with Steam; the first login creates an account (its email is null; identities holds {"provider":"steam","subject":"<SteamID64 as a string>"}). With the Bearer token of a login younger than 10 minutes, the same request links Steam to that account instead (otherwise 403 reauthentication_required). The route answers 404 when the server has no Steam login configured. Steam login is for game clients with the Steam SDK; web pages use email + password.

Bans#

A banned account gets 403 banned on login, refresh and every authenticated request, with details.until (unix ms) for a timed ban. Its sessions are revoked and its WebSockets closed with 4003. Show the ban and do not retry automatically.

Roles#

Roles are strings in account.roles (admin, moderator, a game's own); a normal player has none. The admin routes need admin; deleting other players' chat messages needs admin or moderator (server setting).

Generated from API.md (repository commit e25edb9, file sha256 3db87f7bba70).