Skip to content

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.

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.

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

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.

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)
authenticatedofflineinsecure
Identity verifiedYes (JWT + session service)NoNo
Session service contactedYesNoNo
Who can connectAnyone with valid tokenOwner onlyAnyone
Requires --singleplayerNoYesNo
TransportQUIC onlyQUIC or TCPQUIC or TCP
Password protectionYesYes (owner exempt)Yes
Max players checked at handshakeYesNoNo

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.

The handshake progresses through these states:

StateDescription
REQUESTING_AUTH_GRANTServer requesting grant from session service
AWAITING_AUTH_TOKENServer waiting for client’s access token
PROCESSING_AUTH_TOKENValidating client’s JWT token
EXCHANGING_SERVER_TOKENServer exchanging client’s grant for token
AUTHENTICATEDMutual authentication complete

The client initiates connection with a Connect packet (ID 0):

FieldTypeDescription
protocolCrcintProtocol version CRC
protocolBuildNumberintProtocol build number
clientVersionString (20)Client version string
clientTypeenumGame (0) or Editor (1)
languageStringLocale (e.g., “en-US”), defaults to “en-US” if empty
identityTokenString?Identity JWT (null in offline/insecure modes)
uuidUUIDPlayer UUID
usernameString (16)Player username
referralDatabyte[]?Transfer payload, max 4096 bytes (see Server Transfer)
referralSourceHostAddress?Source server if transferred

ClientType values:

  • Game (0): Regular game client
  • Editor (1): Asset editor client (requires hytale:editor scope)

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
EndpointMethodPurpose
/.well-known/jwks.jsonGETPublic keys for JWT validation
/server-join/auth-grantPOSTRequest authorization grant for a player
/server-join/auth-tokenPOSTExchange authorization grant for access token
/game-session/newPOSTCreate new game session (server auth)
/game-session/refreshPOSTRefresh existing session tokens
/game-sessionDELETETerminate game session

Base URL: https://sessions.hytale.com

ServiceURLPurpose
Session Servicehttps://sessions.hytale.comAuthentication & tokens
Account Datahttps://account-data.hytale.comPlayer profiles

Servers must authenticate with the session service to accept player connections. This can be done via:

  1. CLI/Environment tokens - Pass --session-token and --identity-token at startup, or set HYTALE_SERVER_SESSION_TOKEN and HYTALE_SERVER_IDENTITY_TOKEN environment variables
  2. OAuth browser flow - Use /auth login browser command
  3. OAuth device flow - Use /auth login device command

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

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"
}

The server validates the client’s identity token:

  1. Structure: Valid 3-part JWT format
  2. Algorithm: Must be EdDSA (Ed25519)
  3. Signature: Signed by a key from the session service’s JWKS endpoint
  4. Issuer: Must be https://sessions.hytale.com
  5. Expiration: Not expired (with 5-minute clock skew tolerance)
  6. Not-before / Issued-at: Token is not from the future (same 5-minute tolerance)
  7. Subject UUID: Valid UUID format, matches the UUID from the Connect packet
  8. Scope: Has required scope (hytale:client for Game clients, hytale:editor for 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#S256 claim 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.

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
}

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
}

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
}

Servers can require a password. When enabled:

  1. Server generates a 32-byte random challenge via SecureRandom
  2. Challenge is included in ServerAuthToken.passwordChallenge (authenticated mode) or ConnectAccept.passwordChallenge (offline/insecure mode)
  3. Client computes SHA-256(challenge + password bytes) and sends the hash in PasswordResponse (ID 15)
  4. Server validates and sends PasswordAccepted (ID 16) or PasswordRejected (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.

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.

Authentication failures result in disconnect with a reason string:

ReasonDescription
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 tokenIdentity JWT validation failed
Invalid identity token: UUID mismatchToken subject UUID != Connect packet UUID
Invalid identity token: missing hytale:client scopeGame client missing required scope
Invalid identity token: missing hytale:editor scopeEditor client missing required scope
Invalid access tokenAccess token JWT validation failed
Invalid token claims: UUID mismatchAccess token UUID != Connect packet UUID
Invalid token claims: username mismatchAccess token username != Connect packet username
Mutual authentication required - please update your clientClient didn’t provide server authorization grant
Server authentication unavailable - please try again laterServer has no session tokens configured
Failed to obtain authorization grant from session serviceSession service request failed
Too many players!Server is full (authenticated mode only)
Too many failed password attemptsExceeded 3 password attempts
Editor isn't supported on this server!Editor client on a non-editor server

The authentication flow enforces timeouts at each phase. These are configurable via config.json timeout profiles:

PhaseConfig key
Initial connection (before Connect packet)initial
Overall auth handshakeauth
Auth grant request to session serviceauthGrant
Awaiting client’s AuthToken responseauthToken
Server token exchange with session serviceauthServerExchange
Password responsepassword

Timeouts result in automatic disconnection.

After authentication, the connection uses ping/pong for latency measurement:

Ping packet (ID 2):

public int id; // sequence number
public InstantData time; // timestamp when sent
public 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 ID
public InstantData time; // original timestamp echoed back
public PongType type; // Raw, Direct, or Tick
public short packetQueueSize; // server's packet queue length

Three ping measurements are tracked:

  • Raw: Direct network round-trip time
  • Direct: Processing-adjusted timing
  • Tick: Aligned to game tick boundaries