Official PHP client for SDKey license authentication.
Implements the sealed session protocol: Ed25519-verified handshake, HKDF session keys, and AES-256-GCM validate envelopes, plus plaintext client auth (register / login / upgrade). See PROTOCOL.md.
Version: 0.2.0 (Packagist / git tag v0.2.0)
composer require sdkey/sdkRequires PHP 8.1+ with ext-sodium, ext-openssl, ext-json, and ext-curl.
Embed these values from the SDKey dashboard when you ship your app. appVersion must exactly match the application version configured in SDKey (clientVersion on the wire); mismatch → APP_OUTDATED.
<?php
use Sdkey\SdkeyClient;
use Sdkey\SdkeyError;
$client = new SdkeyClient(
apiBaseUrl: 'https://api.sdkey.dev',
appId: 'YOUR_APP_ID',
appVersion: '1.0.0',
appPublicKeyB64: 'YOUR_APP_PUBLIC_KEY_BASE64',
);
try {
// hwid is optional — omit for web clients
$result = $client->validate('SDKY-XXXX-XXXX-XXXX-XXXX', 'machine-hwid');
if ($result->success) {
echo "licensed {$result->status} tier={$result->subscriptionTier} {$result->message}\n";
} else {
fwrite(STDERR, "denied {$result->code} {$result->message}\n");
}
} catch (SdkeyError $err) {
fwrite(STDERR, "{$err->code} {$err->getMessage()}\n");
throw $err;
}validate calls init() automatically when no session exists. Sessions last ~15 minutes server-side; on SESSION_EXPIRED the client clears local state so the next call re-handshakes.
$auth = $client->register('player1', 'password123', licenseKey: 'SDKY-XXXX');
// or: $client->login('player1', 'password123');
// or: $client->upgrade('player1', 'SDKY-HIGHER'); // no password
echo $auth->sessionToken; // opaque user session (7-day TTL), not crypto sessionIdPer-app responseMessages may customize user-facing strings. The SDK returns those strings as-is.
| Surface | Success text field | Failure text field |
|---|---|---|
| Session init | (none) | error (via SdkeyError message + code) |
| Sealed validate | message (ValidateResult::$message) |
message |
| Client register / login / upgrade | (none) | error (via SdkeyError message + code) |
Sealed validate success (user-facing text in message):
{
"success": true,
"code": "OK",
"message": "validated",
"status": "active",
"expiresAt": "2026-01-01T00:00:00.000Z",
"subscriptionTier": 0,
"sessionId": "...",
"timestamp": 1720000001,
"v": 1
}Sealed validate failure (still message, not error):
{
"success": false,
"code": "HWID_MISMATCH",
"message": "Hardware ID mismatch",
"status": null,
"expiresAt": null,
"sessionId": "...",
"timestamp": 1720000001,
"v": 1
}Init / auth failure (text in error):
{
"success": false,
"error": "Client version outdated",
"code": "APP_OUTDATED"
}| Option | Type | Description |
|---|---|---|
apiBaseUrl |
string |
API origin (no trailing slash) |
appId |
string |
Application UUID |
appVersion |
string |
Exact app version → sent as clientVersion |
appPublicKeyB64 |
string |
Raw Ed25519 public key (32 bytes), base64 |
httpPost |
callable |
Optional HTTP POST override (tests / custom transport): (string $url, array $body): array{0:int,1:array} |
init()— challenge handshake; verifies the signed hello; derives the AES session key; sendsclientVersionvalidate(licenseKey, hwid = null)— sealed validate; always decrypts then verifies the Ed25519 signature before trustingsuccess. Omithwidfor web (JSON key not sent)register(username, password, email?, licenseKey?, hwid?)—POST /api/v1/client/registerlogin(username, password, hwid?)—POST /api/v1/client/loginupgrade(username, licenseKey, hwid?)—POST /api/v1/client/upgrade(no password)getSession()/clearSession()— inspect or drop the local crypto session
Protocol / transport failures and init/auth denials throw SdkeyError with a code (server code when present) and message (server error when present):
INIT_FAILED · AUTH_FAILED · HELLO_SIGNATURE_INVALID · VALIDATE_RESPONSE_INVALID · RESPONSE_SIGNATURE_INVALID · SESSION_MISMATCH · CLOCK_SKEW · NETWORK · plus server codes such as APP_OUTDATED, TIER_NOT_HIGHER, …
License denials on sealed validate (banned, HWID mismatch, etc.) return a normal ValidateResult with success: false — they are not thrown.
This package does not include developer tooling / Bearer management APIs.
- Never ship app private keys in a client.
- Do not skip signature verification — that is the anti-spoof binding.
- This package is open source; the SDKey server remains a separate product.
composer install
composer testMIT