Authentication
Hytale uses a mutual authentication system where both the client and server verify each other’s identity through a session service. This ensures players connect to legitimate servers and servers can verify player identity.
Authentication Modes
Section titled “Authentication Modes”The server’s —auth-mode flag controls how player connections are verified. This determines the entire handshake flow — whether tokens are validated, whether the session service is contacted, and how player identity is established.
authenticated (default)
Section titled “authenticated (default)”The full mutual authentication flow described in the connection flow below. Both the client’s identity token and the server’s session are verified through the session service. The server must be authenticated with the session service (via —session-token or /auth login) for this to work.
- Client must provide a valid identity token (JWT)
- Server validates the JWT signature, issuer, expiration, UUID, and scope (see Token Validation)
- Server and client exchange authorization grants through the session service
- Access tokens are validated with optional mTLS certificate binding
- UUID and username are cryptographically verified — clients cannot spoof identity
- Only works over QUIC transport (TCP connections are rejected in this mode)
- Max player limit is enforced during the handshake
offline
Section titled “offline”Skips all token validation and session service calls. Restricted to singleplayer use only.
- Requires —singleplayer flag — rejects connections otherwise
- Only the world owner (matching —owner-uuid) can connect
- No JWT validation, no session service contact
- UUID and username are taken directly from the client’s Connect packet without verification
- The server sends ConnectAccept packet immediately instead of starting the grant exchange
- Password protection still works (but the owner is exempt)
This mode exists for singleplayer worlds that need to work without internet access.
insecure
Section titled “insecure”Skips all token validation and session service calls with no restrictions.
- Any client can connect with any UUID and username — identity is completely unverified
- No singleplayer restriction, no owner check
- The server sends ConnectAccept packet immediately
- Password protection still works
- Works over both QUIC and TCP transport
- Max player limit is not enforced during the handshake (only checked later)
Comparison
Section titled “Comparison”authenticated | offline | insecure | |
|---|---|---|---|
| Identity verified | Yes (JWT + session service) | No | No |
| Session service contacted | Yes | No | No |
| Who can connect | Anyone with valid token | Owner only | Anyone |
Requires --singleplayer | No | Yes | No |
| Transport | QUIC only | QUIC or TCP | QUIC or TCP |
| Password protection | Yes | Yes (owner exempt) | Yes |
| Max players checked at handshake | Yes | No | No |
Connection Flow
Section titled “Connection Flow”The following flow applies to authenticated mode. In offline and insecure modes, the server skips the entire grant exchange and sends ConnectAccept packet immediately after receiving the Connect packet.
Authentication States
Section titled “Authentication States”The handshake progresses through these states:
| State | Description |
|---|---|
REQUESTING_AUTH_GRANT | Server requesting grant from session service |
AWAITING_AUTH_TOKEN | Server waiting for client’s access token |
PROCESSING_AUTH_TOKEN | Validating client’s JWT token |
EXCHANGING_SERVER_TOKEN | Server exchanging client’s grant for token |
AUTHENTICATED | Mutual authentication complete |
Connect Packet
Section titled “Connect Packet”The client initiates connection with a Connect packet (ID 0):
| Field | Type | Description |
|---|---|---|
protocolCrc | int | Protocol version CRC |
protocolBuildNumber | int | Protocol build number |
clientVersion | String (20) | Client version string |
clientType | enum | Game (0) or Editor (1) |
language | String | Locale (e.g., “en-US”), defaults to “en-US” if empty |
identityToken | String? | Identity JWT (null in offline/insecure modes) |
uuid | UUID | Player UUID |
username | String (16) | Player username |
referralData | byte[]? | Transfer payload, max 4096 bytes (see Server Transfer) |
referralSource | HostAddress? | Source server if transferred |
ClientType values:
Game(0): Regular game clientEditor(1): Asset editor client (requireshytale:editorscope)
Session Service
Section titled “Session Service”The Session Service is Hytale’s official authentication backend operated by Hypixel Studios. It handles:
- Player identity verification
- Server authentication
- Token issuance and validation
- Authorization grant exchanges
Endpoints
Section titled “Endpoints”| Endpoint | Method | Purpose |
|---|---|---|
/.well-known/jwks.json | GET | Public keys for JWT validation |
/server-join/auth-grant | POST | Request authorization grant for a player |
/server-join/auth-token | POST | Exchange authorization grant for access token |
/game-session/new | POST | Create new game session (server auth) |
/game-session/refresh | POST | Refresh existing session tokens |
/game-session | DELETE | Terminate game session |
Base URL: https://sessions.hytale.com
Related Services
Section titled “Related Services”| Service | URL | Purpose |
|---|---|---|
| Session Service | https://sessions.hytale.com | Authentication & tokens |
| Account Data | https://account-data.hytale.com | Player profiles |
Server Authentication
Section titled “Server Authentication”Servers must authenticate with the session service to accept player connections. This can be done via:
- CLI/Environment tokens - Pass
--session-tokenand--identity-tokenat startup, or setHYTALE_SERVER_SESSION_TOKENandHYTALE_SERVER_IDENTITY_TOKENenvironment variables - OAuth browser flow - Use
/auth login browsercommand - OAuth device flow - Use
/auth login devicecommand
Once authenticated, the server obtains:
- Session token: JWT for server-to-service API calls
- Identity token: JWT proving server identity to clients
- Certificate fingerprint: SHA-256 hash of the server’s TLS certificate
API Examples
Section titled “API Examples”Request authorization grant (POST /server-join/auth-grant):
// request (Authorization: Bearer <session-token>){ "identityToken": "<player-identity-jwt>", "aud": "<server-audience>"}
// response{ "authorizationGrant": "<grant-string>"}Exchange grant for token (POST /server-join/auth-token):
// request (Authorization: Bearer <session-token>){ "authorizationGrant": "<grant-from-client>", "x509Fingerprint": "<server-cert-sha256>"}
// response{ "accessToken": "<access-jwt>"}Create game session (POST /game-session/new):
// request (Authorization: Bearer <oauth-access-token>){ "uuid": "<profile-uuid>"}
// response{ "sessionToken": "<session-jwt>", "identityToken": "<identity-jwt>", "expiresAt": "2024-01-15T12:00:00Z"}Token Validation
Section titled “Token Validation”The server validates the client’s identity token:
- Structure: Valid 3-part JWT format
- Algorithm: Must be EdDSA (Ed25519)
- Signature: Signed by a key from the session service’s JWKS endpoint
- Issuer: Must be
https://sessions.hytale.com - Expiration: Not expired (with 5-minute clock skew tolerance)
- Not-before / Issued-at: Token is not from the future (same 5-minute tolerance)
- Subject UUID: Valid UUID format, matches the UUID from the Connect packet
- Scope: Has required scope (
hytale:clientfor Game clients,hytale:editorfor Editor clients)
The access token (from the AuthToken packet) is additionally validated for:
- Audience: Must match the expected server audience
- Certificate binding: The
cnf.x5t#S256claim must match the client’s mTLS certificate fingerprint - Username: Must be present and match the username from the Connect packet
JWKS public keys are cached and only refreshed on signature failure, with a 5-minute minimum interval between refreshes.
If any validation fails, the client is disconnected with an error message.
AuthGrant Packet
Section titled “AuthGrant Packet”After validating the identity token, the server sends an AuthGrant (ID 11):
public class AuthGrant implements Packet { public String authorizationGrant; // grant for client to exchange public String serverIdentityToken; // server's identity for verification}AuthToken Packet
Section titled “AuthToken Packet”The client exchanges the grant with the session service and sends back AuthToken (ID 12):
public class AuthToken implements Packet { public String accessToken; // client's access token (JWT) public String serverAuthorizationGrant; // grant for server verification}ServerAuthToken Packet
Section titled “ServerAuthToken Packet”Server confirms mutual auth by sending ServerAuthToken (ID 13):
public class ServerAuthToken implements Packet { public String serverAccessToken; // server's verified access token public byte[] passwordChallenge; // optional 32-byte challenge}Password Protection
Section titled “Password Protection”Servers can require a password. When enabled:
- Server generates a 32-byte random challenge via
SecureRandom - Challenge is included in
ServerAuthToken.passwordChallenge(authenticated mode) orConnectAccept.passwordChallenge(offline/insecure mode) - Client computes
SHA-256(challenge + password bytes)and sends the hash inPasswordResponse(ID 15) - Server validates and sends
PasswordAccepted(ID 16) orPasswordRejected(ID 17)
On rejection, the server generates a new challenge and includes it in PasswordRejected.newChallenge along with attemptsRemaining. The client gets a maximum of 3 attempts before being disconnected.
Exception: In singleplayer mode, the world owner UUID is exempt from password checks.
mTLS (Mutual TLS)
Section titled “mTLS (Mutual TLS)”QUIC transport requires mutual TLS:
- Server presents its certificate
- Client presents its certificate
- Certificate fingerprints (SHA-256) are exchanged with the session service during the grant flow
The client certificate fingerprint is bound to the access token via the cnf.x5t#S256 JWT claim, providing an additional layer of identity verification beyond the token itself.
Disconnect Reasons
Section titled “Disconnect Reasons”Authentication failures result in disconnect with a reason string:
| Reason | Description |
|---|---|
This server requires authentication! | No identity token provided in authenticated mode |
TCP connections only support insecure authentication... | Authenticated connection attempted over TCP |
Offline mode is only available in singleplayer. | Offline mode without --singleplayer |
This world is in offline mode and only the owner can connect. | Non-owner in offline mode |
Invalid or expired identity token | Identity JWT validation failed |
Invalid identity token: UUID mismatch | Token subject UUID != Connect packet UUID |
Invalid identity token: missing hytale:client scope | Game client missing required scope |
Invalid identity token: missing hytale:editor scope | Editor client missing required scope |
Invalid access token | Access token JWT validation failed |
Invalid token claims: UUID mismatch | Access token UUID != Connect packet UUID |
Invalid token claims: username mismatch | Access token username != Connect packet username |
Mutual authentication required - please update your client | Client didn’t provide server authorization grant |
Server authentication unavailable - please try again later | Server has no session tokens configured |
Failed to obtain authorization grant from session service | Session service request failed |
Too many players! | Server is full (authenticated mode only) |
Too many failed password attempts | Exceeded 3 password attempts |
Editor isn't supported on this server! | Editor client on a non-editor server |
Timeouts
Section titled “Timeouts”The authentication flow enforces timeouts at each phase. These are configurable via config.json timeout profiles:
| Phase | Config key |
|---|---|
| Initial connection (before Connect packet) | initial |
| Overall auth handshake | auth |
| Auth grant request to session service | authGrant |
| Awaiting client’s AuthToken response | authToken |
| Server token exchange with session service | authServerExchange |
| Password response | password |
Timeouts result in automatic disconnection.
Ping/Pong
Section titled “Ping/Pong”After authentication, the connection uses ping/pong for latency measurement:
Ping packet (ID 2):
public int id; // sequence numberpublic InstantData time; // timestamp when sentpublic int lastPingValueRaw; // previous raw ping (ms)public int lastPingValueDirect; // previous direct ping (ms)public int lastPingValueTick; // previous tick-aligned ping (ms)Pong packet (ID 3):
public int id; // matches ping IDpublic InstantData time; // original timestamp echoed backpublic PongType type; // Raw, Direct, or Tickpublic short packetQueueSize; // server's packet queue lengthThree ping measurements are tracked:
- Raw: Direct network round-trip time
- Direct: Processing-adjusted timing
- Tick: Aligned to game tick boundaries