Account Authentication

Account Authentication

Passwords plus 12-word BIP39 recovery phrases, Argon2id hashing, 7-day session tokens, and the WebSocket identify flow.

Account Authentication

#

Credentials

An account has two independent secrets:

  • A password used for login and for destructive account actions.
  • A recovery phrase of 12 BIP39 words used only to reset the password.

Both secrets are stored as Argon2id hashes. The plaintext is never persisted by the server.

password         -> Argon2id -> users.password_hash
    recovery phrase  -> Argon2id -> users.recovery_hash

Password and recovery hashing

The core/security.rs module owns secret handling.

FunctionBehavior
hash_secretArgon2id (Argon2::default()) with a random salt.
verify_secretArgon2id verification against a stored hash.
verify_secret_constant_timeVerifies against a fixed dummy hash when no real hash exists, then returns the same generic failure. Lifts the timing signal that would otherwise reveal whether a username exists.
token_hashSHA256(session token) as lowercase hex.
generate_session_token64 alphanumeric characters from the OS CSPRNG.
generate_recovery_wordsA BIP39 mnemonic from 16 bytes of entropy, which produces 12 words.

Username validation normalizes to lowercase and trimmed form, enforces a length of 2 to 24 characters at registration, rejects a reserved system name, and allows only a fixed whitelist of characters. Passwords must be 8 to 128 characters.

Registration

The POST /api/auth/register endpoint and the services/auth.rs::register function create an account.

Steps:

  • Verify that registration is enabled in the feature flags.
  • Reject usernames that hit the operator blocklist.
  • Validate the username and password.
  • If the username is taken, burn a constant-time dummy hash and return a generic error.
  • Generate a random snowflake account id.
  • Generate 12 recovery words and hash them.
  • Hash the password.
  • Insert the user row.
  • Create a session token.

The response is:

{
      "ok": true,
      "token": "<session token>",
      "user": { "...": "..." },
      "recoveryWords": ["word1", "word2", "..."]
    }

The recovery words are returned exactly once, at registration. They are also available for download from the client after a successful registration.

Login

The POST /api/auth/login endpoint and services/auth.rs::login function authenticate a password.

Steps:

  • Normalize the username.
  • Look up the user.
  • Verify the password with Argon2id.
  • Reject banned or disabled accounts.
  • On a missing user, burn the constant-time dummy hash.
  • Create a session token.

The response is { "ok": true, "token": "...", "user": { ... } }.

Session tokens

A session token is a 64 character alphanumeric string. The server stores only SHA256(token) in the sessions table.

ColumnMeaning
token_hashPrimary key, the SHA-256 hex of the token.
user_idThe owning account.
created_atCreation time in milliseconds.
expires_atExpiry time in milliseconds.

The session TTL is 7 days (SESSION_TTL_MS). authenticate_token resolves a token to the user, rejecting expired tokens and disabled or banned accounts. touch_session extends the TTL. logout deletes the session row. Recovery and password changes invalidate all sessions for the account.

Recovery

The POST /api/auth/recover endpoint resets a password using the recovery phrase.

Steps:

  • Normalize and validate the new password.
  • Look up the user.
  • Verify the normalized recovery phrase against recovery_hash.
  • Reject banned accounts.
  • Hash the new password and write it.
  • Delete all existing sessions for the account.
  • Create a fresh session token.

The recovery phrase is normalized the same way as at creation: split on whitespace, lowercased, joined with single spaces.

WebSocket identification

The WebSocket protocol identifies a connection with opcode 2. The client sends a token in either the token or authToken field. The server calls authenticate_token and, on success, binds the session to a player record with the account username, id, admin flag, badges, profile, and presence status.

The identification response includes the optional default room record:

{
      "op": 2,
      "d": {
        "userId": "...",
        "username": "...",
        "admin": false,
        "profile": { "...": "..." },
        "status": "online",
        "badges": [],
        "defaultRoom": { "roomId": "...", "roomKey": "...", "title": "..." }
      }
    }

HTTP bearer authentication

Authenticated HTTP endpoints use the Authorization: Bearer <token> header. authenticated_user resolves the token through authenticate_token. The endpoints that require a bearer token include the social blob, profile image upload, username change, and the admin endpoints.

Account deletion

Deletion requires the current password. On success the server removes:

  • All sessions for the account.
  • The prekey bundle.
  • The block tags.
  • The user row.

It then disconnects all live sessions for the account, invalidates the public profile cache, and removes any uploaded avatar and banner files.