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
LogPrefix
Log source tag for all log messages.
protected virtual string LogPrefix { get; }
Property Value
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
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
emailstringThe email address to validate.
Returns
- bool
trueif 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
passwordstringThe password string to validate.
Returns
- bool
trueif 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
usernamestringThe username string to validate.
Returns
- bool
trueif 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
resultClientAuthenticationResult
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
resultClientAuthenticationResultThe 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
connectionTokenstringOne-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
serverPublicKeybyte[]Server's X25519 public key, or null for a cookie challenge.
cookiebyte[]Cookie from a phase-1 challenge.
agreedVersionushortNegotiated 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
encryptedServerProofbyte[]Encrypted server proof.
resultClientAuthenticationResultAuth result code sent alongside the proof.
encryptedTokenbyte[]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
encryptedSaltbyte[]Encrypted SRP salt from server.
encryptedServerEphemeralbyte[]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
otpauthUristringotpauth URI for authenticator app QR code.
recoveryCodesstring[]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
encryptedOtpauthUribyte[]Encrypted otpauth URI bytes.
encryptedRecoveryCodesbyte[]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
encryptedUsernamebyte[]AES-GCM encrypted username.
encryptedCodebyte[]AES-GCM encrypted verification code.
sequintMessage 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
publicKeybyte[]Client's ephemeral X25519 public key (32 bytes).
cookiebyte[]Cookie echoed from a prior challenge, or null on the initial handshake.
connectionTokenstringminVersionushortMinimum protocol version supported by this client.
maxVersionushortMaximum protocol version supported by this client.
gameVersionstring
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
encryptedUsernamebyte[]AES-GCM encrypted username.
encryptedEmailbyte[]AES-GCM encrypted email address.
encryptedAgebyte[]AES-GCM encrypted age value.
encryptedSaltbyte[]AES-GCM encrypted SRP salt.
encryptedVerifierbyte[]AES-GCM encrypted SRP verifier.
sequintMessage sequence number.
SendSrpProof(byte[], uint)
Sends the SRP proof broadcast (login path, phase 2).
protected abstract void SendSrpProof(byte[] encryptedProof, uint seq)
Parameters
SendSrpVerify(byte[], byte[], uint)
Sends the SRP verify broadcast (login path, phase 1).
protected abstract void SendSrpVerify(byte[] encryptedUsername, byte[] encryptedClientEphemeral, uint seq)
Parameters
encryptedUsernamebyte[]AES-GCM encrypted username bytes.
encryptedClientEphemeralbyte[]AES-GCM encrypted SRP client public ephemeral.
sequintMessage sequence number.
SendTokenAuth(byte[], uint)
Sends a token auth broadcast (World/Scene server path).
protected abstract void SendTokenAuth(byte[] encryptedToken, uint seq)
Parameters
SendTotpCode(string)
Encrypts and sends a TOTP code for two-factor verification.
public void SendTotpCode(string code)
Parameters
codestringThe 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
SendVerifyCode(string, string)
Encrypts and sends an account verification code.
public void SendVerifyCode(string username, string verifyCode)
Parameters
usernamestringAccount username to verify.
verifyCodestringThe 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
versionstringGame 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
usernamestringUsername or email used as login identifier.
passwordstringAccount password.
registerboolTrue to register a new account; false to login.
emailstringEmail address (required for registration).
ageintUser 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
rawTokenbyte[]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
encryptedTokenbyte[]AES-GCM encrypted auth token bytes from the server.
Returns
- bool
trueif the token was decrypted and stored;falseon 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
tokenCopybyte[]Defensive copy of the stored token, or
nullwhen none was held.
Returns
- bool
truewhen a token was returned;falsewhen none was held.