Client code
Python, C# and C++
Complete, single-file clients for the client API. Each one sends fresh nonces, verifies every response signature, binds the device and keeps the session alive with heartbeats.
The easiest way: open your application in the dashboard. Its Overview → Connect your software section shows this code with your API URL, App ID and public key already filled in.
install
pip install requests cryptographysauth_client.py
"""SAUTH client for your application.
Setup: pip install requests cryptography
Usage: see the bottom of this file.
"""
import base64
import datetime
import hashlib
import secrets
import subprocess
import sys
import threading
import time
import uuid
import requests
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
API_URL = "https://<your-api-domain>/v1/client"
APP_ID = "<your App ID>"
PUBLIC_KEY = "<your public key>"
VERSION = "1.0.0" # your program's version
class SauthError(Exception):
def __init__(self, code: str, message: str):
super().__init__(message)
self.code = code
def device_id() -> str:
"""A stable id for this computer. Only a SHA-256 hash of it is sent."""
raw = ""
try:
if sys.platform == "win32":
import winreg
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\Cryptography",
0,
winreg.KEY_READ | winreg.KEY_WOW64_64KEY,
)
raw = winreg.QueryValueEx(key, "MachineGuid")[0]
elif sys.platform == "darwin":
out = subprocess.check_output(["ioreg", "-rd1", "-c", "IOPlatformExpertDevice"], text=True)
raw = out.split('"IOPlatformUUID" = "')[1].split('"')[0]
else:
with open("/etc/machine-id") as f:
raw = f.read().strip()
except Exception:
raw = ""
return hashlib.sha256((raw or str(uuid.getnode())).encode()).hexdigest()
def expiry_text(data: dict) -> str:
"""'Lifetime', 'No license' or the local expiry date of a sign-in/session response."""
license = data.get("license")
if not license:
return "No license"
if not license.get("expiresAt"):
return "Lifetime" # null expiresAt = never expires
when = datetime.datetime.fromisoformat(license["expiresAt"].replace("Z", "+00:00"))
return when.astimezone().strftime("%Y-%m-%d %H:%M")
class Sauth:
def __init__(self):
self._key = Ed25519PublicKey.from_public_bytes(base64.b64decode(PUBLIC_KEY))
self._http = requests.Session()
self._lock = threading.Lock()
self.session_token = None
self.hwid = device_id()
def call(self, type_: str, **params) -> dict:
"""Sends one request and returns the verified JSON response."""
with self._lock:
nonce = secrets.token_hex(16)
body = {"type": type_, "nonce": nonce, "timestamp": int(time.time() * 1000)}
body.update({k: v for k, v in params.items() if v is not None})
if self.session_token and type_ != "init":
body["sessionToken"] = self.session_token
r = self._http.post(API_URL, json=body, timeout=20)
if not r.ok:
try:
err = r.json()["error"]
raise SauthError(err["code"], err["message"])
except (ValueError, KeyError, TypeError):
raise SauthError("HTTP_%d" % r.status_code, "Unexpected server response.")
# Never trust a response that isn't signed with your application's key.
try:
message = (r.headers["X-Sauth-Timestamp"] + "." + nonce + ".").encode() + r.content
self._key.verify(base64.b64decode(r.headers["X-Sauth-Signature"]), message)
except (KeyError, ValueError, InvalidSignature):
raise SauthError("BAD_SIGNATURE", "The response signature is invalid.")
return r.json()
# ── Operations ──────────────────────────────────────────────────────────
def init(self) -> dict:
self.session_token = None
data = self.call("init", appId=APP_ID, version=VERSION, hwid=self.hwid)
self.session_token = data["sessionToken"]
return data
def license(self, key: str) -> dict:
return self.call("license", key=key, hwid=self.hwid)
def login(self, username: str, password: str) -> dict:
return self.call("login", username=username, password=password, hwid=self.hwid)
def register(self, username: str, password: str, key: str, email: str = None) -> dict:
return self.call(
"register", username=username, password=password, key=key, email=email, hwid=self.hwid
)
def validate(self) -> dict:
return self.call("validate")
def heartbeat(self) -> dict:
return self.call("heartbeat")
def variables(self) -> dict:
return self.call("variables")["variables"]
def info(self) -> dict:
return self.call("info")
def session(self) -> dict:
return self.call("session")
def refresh(self) -> dict:
data = self.call("refresh")
self.session_token = data["sessionToken"]
return data
def hwid_reset(self, reason: str = None) -> dict:
return self.call("hwid-reset", reason=reason)
def logout(self) -> None:
self.call("logout")
self.session_token = None
def start_heartbeat(self, every_seconds: int = 300) -> None:
"""Closes the program if the session is revoked (ban, expiry, key deleted)."""
def loop():
while True:
time.sleep(every_seconds)
try:
self.heartbeat()
except SauthError as e:
print("Session ended: %s" % e)
import os
os._exit(1)
except requests.RequestException:
pass # network hiccup: try again next time
threading.Thread(target=loop, daemon=True).start()
if __name__ == "__main__":
auth = Sauth()
try:
auth.init()
key = input("License key: ").strip()
result = auth.license(key) # or: auth.login(username, password)
print("Expiry :", expiry_text(result))
auth.start_heartbeat()
except SauthError as e:
print("Error: %s (%s)" % (e, e.code))
sys.exit(1)
# ... your program runs here ...
input("Press Enter to exit")
Integration rules
- Never embed secrets. Your software needs only its API URL, App ID and public signing key — all three are public.
- Verify every response signature before trusting its contents. The code above does this; if you write your own client, it must too.
- Fetch protected content from the server. Values that unlock features — download links, configuration, keys for your own services — should come from variables after sign-in, not be compiled into the binary.
- Handle errors by code. Show your own message or the one configured in the dashboard; don't parse message text.
- Send heartbeats so revocations and bans take effect while your program is running.