Table of Contents

Class ClientAuthenticatorCore

Namespace
FishMMO.Auth.Implementation
Assembly
FishMMO-ClientAuth.dll

Engine-independent client-side authenticator state machine. Implements the full SRP-6a + X25519 ECDH client auth flow, including cookie challenge echo, key agreement, token auth (World/Scene), SRP verify/proof, TOTP, and key material cleanup.

Concrete implementations (e.g., FishNet ClientLoginAuthenticator) implement abstract callbacks for broadcasting messages and notifying the application layer.

public abstract class ClientAuthenticatorCore
Inheritance
ClientAuthenticatorCore
Inherited Members

Properties

HasAuthToken

Returns whether a stored auth token exists for World/Scene server authentication.

public bool HasAuthToken { get; }

Property Value

bool

LogPrefix

Log source tag for all log messages.

protected virtual string LogPrefix { get; }

Property Value

string

PendingLoginIdentifier

Returns the pending login identifier (username or email) if still set. Returns null after credentials are cleared (post-SRP proof or disconnect).

public string? PendingLoginIdentifier { get; }

Property Value

string

Methods

ClearAuthToken()

Zeroes and clears the stored auth token. Call on explicit logout or when the token is no longer needed.

public void ClearAuthToken()

ClearKeyMaterial()

Zeroes all per-connection key material and resets state. Does NOT clear FishMMO.Auth.Implementation.ClientAuthenticatorCore.storedAuthToken.

public void ClearKeyMaterial()

Disconnect()

Disconnects the client. Called on fatal protocol errors.

protected abstract void Disconnect()

Dispose()

Disposes the ephemeral keypair. Call from the host object's destroy/dispose method.

public void Dispose()

IsAllowedEmailUsername(string)

Validates an email-format username. Delegates to Authentication.IsAllowedEmailUsername.

protected abstract bool IsAllowedEmailUsername(string email)

Parameters

email string

The email address to validate.

Returns

bool

true if the email is a valid login identifier; otherwise, false.

IsAllowedPassword(string)

Validates a password according to project rules. Delegates to Authentication.IsAllowedPassword.

protected abstract bool IsAllowedPassword(string password)

Parameters

password string

The password string to validate.

Returns

bool

true if the password is allowed; otherwise, false.

IsAllowedUsername(string)

Validates a username according to project rules. Delegates to Authentication.IsAllowedUsername.

protected abstract bool IsAllowedUsername(string username)

Parameters

username string

The username string to validate.

Returns

bool

true if the username is allowed; otherwise, false.

OnAuthResultCallback(ClientAuthenticationResult)

Invoked when an auth result is received from the server. Implementations should fire a UI event or property change.

protected abstract void OnAuthResultCallback(ClientAuthenticationResult result)

Parameters

result ClientAuthenticationResult

OnAuthResultReceived(ClientAuthenticationResult)

Handles a generic auth result broadcast from the server. Clears the stored token on terminal token failures.

public void OnAuthResultReceived(ClientAuthenticationResult result)

Parameters

result ClientAuthenticationResult

The auth result code.

OnConnected(string?)

Call when a new transport connection is established. Generates the X25519 keypair and sends the initial ClientHandshake.

public void OnConnected(string? connectionToken = null)

Parameters

connectionToken string

One-time token from the IPFetch HTTP API. Used on initial Login Server connection for real-IP recovery. Null for World/Scene reconnections.

OnDisconnected()

Call when the transport connection stops or is stopped. Clears all per-connection key material (not the stored auth token).

public void OnDisconnected()

OnRehandshakeRequired()

Resets the per-connection cryptographic state so a fresh ClientHandshake can be sent on the same transport connection, while keeping the credentials the pending login still needs.

public void OnRehandshakeRequired()

Remarks

The login queue defers the handshake before SRP begins and later invites the client to handshake again on the connection it has been holding open. That retry has to throw away the ephemeral keypair, the derived session keys and the nonce contexts — the server generates a new set for the new handshake — but it must NOT throw away the username and password, because SRP has not run yet and nothing will ever supply them again.

Reusing OnDisconnected() for this did exactly that: it calls ClearKeyMaterial(), which nulls the credentials along with the keys, so the re-handshake reached the credential pre-validation in OnServerHandshakeReceived(byte[], byte[], ushort) with an empty username and disconnected itself. Every client that was queued was therefore dropped the instant it reached the front of the queue, with no message — the queue could never admit anybody.

OnConnected(string?) is what the caller invokes next; it regenerates the keypair and resets the duplicate-message guards, which a second handshake on one connection also depends on.

OnServerHandshakeReceived(byte[], byte[], ushort)

Handles a server handshake response. Phase 1 (cookie challenge): echoes the cookie with the public key. Phase 2 (ECDH complete): derives session keys, then initiates SRP or token auth.

public void OnServerHandshakeReceived(byte[] serverPublicKey, byte[] cookie, ushort agreedVersion)

Parameters

serverPublicKey byte[]

Server's X25519 public key, or null for a cookie challenge.

cookie byte[]

Cookie from a phase-1 challenge.

agreedVersion ushort

Negotiated protocol version (only meaningful on phase 2).

OnSrpSuccessReceived(byte[], ClientAuthenticationResult, byte[])

Handles the SRP success response from the server. Verifies the server proof, extracts and stores the auth token on success.

public void OnSrpSuccessReceived(byte[] encryptedServerProof, ClientAuthenticationResult result, byte[] encryptedToken)

Parameters

encryptedServerProof byte[]

Encrypted server proof.

result ClientAuthenticationResult

Auth result code sent alongside the proof.

encryptedToken byte[]

Encrypted auth token (null if not a LoginSuccess).

OnSrpVerifyResponseReceived(byte[], byte[])

Handles the SRP verify response from the server. Decrypts salt + server ephemeral, computes and sends the SRP proof.

public void OnSrpVerifyResponseReceived(byte[] encryptedSalt, byte[] encryptedServerEphemeral)

Parameters

encryptedSalt byte[]

Encrypted SRP salt from server.

encryptedServerEphemeral byte[]

Encrypted server public ephemeral from server.

OnTwoFactorSetupCallback(string, string[])

Invoked when the server sends 2FA setup data after account creation.

protected abstract void OnTwoFactorSetupCallback(string otpauthUri, string[] recoveryCodes)

Parameters

otpauthUri string

otpauth URI for authenticator app QR code.

recoveryCodes string[]

Recovery codes array.

OnTwoFactorSetupReceived(byte[], byte[])

Handles a 2FA setup broadcast (received after successful account registration). Decrypts the otpauth URI and recovery codes then fires OnTwoFactorSetupCallback(string, string[]).

public void OnTwoFactorSetupReceived(byte[] encryptedOtpauthUri, byte[] encryptedRecoveryCodes)

Parameters

encryptedOtpauthUri byte[]

Encrypted otpauth URI bytes.

encryptedRecoveryCodes byte[]

Encrypted recovery codes array.

SendAccountVerify(byte[], byte[], uint)

Sends an account verification code broadcast.

protected abstract void SendAccountVerify(byte[] encryptedUsername, byte[] encryptedCode, uint seq)

Parameters

encryptedUsername byte[]

AES-GCM encrypted username.

encryptedCode byte[]

AES-GCM encrypted verification code.

seq uint

Message sequence number.

SendClientHandshake(byte[], byte[]?, string?, ushort, ushort, string)

Sends the initial or cookie-echo client handshake broadcast.

protected abstract void SendClientHandshake(byte[] publicKey, byte[]? cookie, string? connectionToken, ushort minVersion, ushort maxVersion, string gameVersion)

Parameters

publicKey byte[]

Client's ephemeral X25519 public key (32 bytes).

cookie byte[]

Cookie echoed from a prior challenge, or null on the initial handshake.

connectionToken string
minVersion ushort

Minimum protocol version supported by this client.

maxVersion ushort

Maximum protocol version supported by this client.

gameVersion string

SendCreateAccount(byte[], byte[], byte[], byte[], byte[], uint)

Sends the account creation broadcast (registration path).

protected abstract void SendCreateAccount(byte[] encryptedUsername, byte[] encryptedEmail, byte[] encryptedAge, byte[] encryptedSalt, byte[] encryptedVerifier, uint seq)

Parameters

encryptedUsername byte[]

AES-GCM encrypted username.

encryptedEmail byte[]

AES-GCM encrypted email address.

encryptedAge byte[]

AES-GCM encrypted age value.

encryptedSalt byte[]

AES-GCM encrypted SRP salt.

encryptedVerifier byte[]

AES-GCM encrypted SRP verifier.

seq uint

Message sequence number.

SendSrpProof(byte[], uint)

Sends the SRP proof broadcast (login path, phase 2).

protected abstract void SendSrpProof(byte[] encryptedProof, uint seq)

Parameters

encryptedProof byte[]

AES-GCM encrypted SRP client proof.

seq uint

Message sequence number.

SendSrpVerify(byte[], byte[], uint)

Sends the SRP verify broadcast (login path, phase 1).

protected abstract void SendSrpVerify(byte[] encryptedUsername, byte[] encryptedClientEphemeral, uint seq)

Parameters

encryptedUsername byte[]

AES-GCM encrypted username bytes.

encryptedClientEphemeral byte[]

AES-GCM encrypted SRP client public ephemeral.

seq uint

Message sequence number.

SendTokenAuth(byte[], uint)

Sends a token auth broadcast (World/Scene server path).

protected abstract void SendTokenAuth(byte[] encryptedToken, uint seq)

Parameters

encryptedToken byte[]

AES-GCM encrypted auth token.

seq uint

Message sequence number.

SendTotpCode(string)

Encrypts and sends a TOTP code for two-factor verification.

public void SendTotpCode(string code)

Parameters

code string

The 6-digit TOTP code from the authenticator app.

SendTwoFactorVerify(byte[], uint)

Sends a TOTP verify broadcast.

protected abstract void SendTwoFactorVerify(byte[] encryptedCode, uint seq)

Parameters

encryptedCode byte[]

AES-GCM encrypted TOTP code.

seq uint

Message sequence number.

SendVerifyCode(string, string)

Encrypts and sends an account verification code.

public void SendVerifyCode(string username, string verifyCode)

Parameters

username string

Account username to verify.

verifyCode string

The verification code received by the user.

SetGameVersion(string)

Stores the client game version to be sent during the handshake. Must be called before OnConnected(string?).

public void SetGameVersion(string version)

Parameters

version string

Game version string (e.g. "0.1.0").

SetLoginCredentials(string, string, bool, string, int)

Sets login credentials. Returns false if the format is invalid.

public bool SetLoginCredentials(string username, string password, bool register = false, string email = "", int age = 0)

Parameters

username string

Username or email used as login identifier.

password string

Account password.

register bool

True to register a new account; false to login.

email string

Email address (required for registration).

age int

User age (required for registration).

Returns

bool

True if credentials were accepted; false if rejected by validation rules.

SetStoredAuthToken(byte[])

Stores a raw auth token for use in the next token auth flow. Intended for test harnesses that need to inject a token without going through the full SRP flow.

protected void SetStoredAuthToken(byte[] rawToken)

Parameters

rawToken byte[]

Raw token bytes to store. Must not be null or empty.

TryApplyRenewedToken(byte[]?)

Decrypts a freshly-minted auth token received mid-session from a World/Scene server (over the existing AES-GCM session channel) and replaces the currently stored token. Used by the reconnect-only token-refresh flow so that future reconnect attempts continue working past the original token's expiration.

public bool TryApplyRenewedToken(byte[]? encryptedToken)

Parameters

encryptedToken byte[]

AES-GCM encrypted auth token bytes from the server.

Returns

bool

true if the token was decrypted and stored; false on missing session keys, empty payload, or decryption failure.

TryConsumeStoredTokenForRevoke(out byte[]?)

Returns a defensive copy of the currently stored auth token (the raw, HMAC-signed bytes the LoginServer issued) for the sole purpose of sending a server-side revocation request, then zeroes and clears the stored copy. Returns false if no token is currently held.

Security note: the returned bytes are transmitted to the server in cleartext (the auth pipeline's AES-GCM channel has typically been torn down by the time the user explicitly logs out). The server hashes the bytes and matches them against the persisted token-hash row, then marks it revoked. Because the token is being revoked anyway, any eavesdropper who captures it gains nothing.

public bool TryConsumeStoredTokenForRevoke(out byte[]? tokenCopy)

Parameters

tokenCopy byte[]

Defensive copy of the stored token, or null when none was held.

Returns

bool

true when a token was returned; false when none was held.