Skip to content

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 cryptography
sauth_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.