CheeseAuth

⚠ Incorrect password β€” try again
Total πŸ—
β€”
all licenses
Active βœ“
β€”
valid & activated
Pending β—·
β€”
not yet activated
Expired βŒ›
β€”
past expiry date
Banned βŠ—
β€”
blocked keys
Online β—‰
β€”
active right now
CheeseAuth API

License key authentication system for desktop applications

What is CheeseAuth?

CheeseAuth is a self-hosted license key validation backend. Any .exe or application can connect to it using a simple HTTP API to validate license keys, bind them to a machine (HWID), and maintain a live session.

How it works
1
Create an AppIn the Admin Panel β†’ Apps section, create a new app and copy its app_secret.
2
Generate KeysGenerate license keys assigned to that App. Keys can be trial, weekly, monthly, lifetime, or custom duration.
3
Integrate the APIIn your .exe, call POST /api/authenticate with the user's key + their HWID + your app_secret. On success you receive a session token.
4
Keep session aliveSend a heartbeat to POST /api/heartbeat every 30 seconds. If the heartbeat fails or the key gets banned, close the application.
Base URL: Replace your-server.com:4000 with your actual server address throughout all examples.
Quickstart
Step 1 β€” Create an App

Go to Apps / Products in the admin panel β†’ click + New App β†’ enter a name (e.g. "MyTool v1") β†’ copy the generated app_secret (format: ca_xxxxxxxxxxxxxxxx).

Step 2 β€” Generate keys for that App

In the Generate New Key section, select your App from the dropdown, choose a duration, and click Generate. Distribute those keys to your users.

Step 3 β€” Integrate in your app

Your application needs to do 3 things:

A
Get a unique HWIDGenerate a persistent machine ID (see HWID section). Store it hidden so it survives reboots but not reinstalls.
B
Call /api/authenticateSend the user's key + HWID + your app_secret. On first use the key activates. On repeat use, HWID must match.
C
Start heartbeat loopEvery 30s send the session token to /api/heartbeat. If it returns false, exit the app immediately.
Important: Never hardcode the key in your app. The user should enter their own key at launch.
POST /api/authenticate

Validates a license key and creates a session. On first use the key gets bound to the provided HWID. Works in two modes depending on whether app_secret is sent or not.

POSThttp://your-server.com:4000/api/authenticate
Request Body (JSON)
FieldTypeRequiredDescription
keystringβœ… AlwaysThe license key entered by the user (e.g. CHEESE-A1B2C3D4-E5F6A7B8)
hwidstringβœ… AlwaysUnique machine identifier β€” binds the key to this machine on first use
app_secretstringβšͺ OptionalYour app's secret (format: ca_...). Required only for direct C++ integration. The Universal Launcher does NOT send this.
Two modes:
With app_secret β€” C++ / custom integration. Key must belong to that specific app.
Without app_secret β€” Universal Launcher mode. Server automatically finds which app the key belongs to and returns its download_url.
Success Response
{
  "success": true,
  "expiry": "12/31/2025",
  "token": "a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5",
  "download_url": "http://your-server.com:4000/files/CheeseEsp.exe",
  "version": "1.0.5",
  "app_name": "CheeseEsp"
}

// download_url, version and app_name are null if the key has no app assigned
// or if the app has no file configured in Set Version.
Failure Response
{
  "success": false,
  "message": "Invalid license key"
}
Possible failure messages
MessageReason
Invalid license keyKey does not exist in database
This key has been bannedManually banned or auto-banned by abuse detection
License expiredPast expiry date
Too many failed attempts for this key...Per-key rate limit hit (5 failures / 10 min)
Key is already active on another device.Concurrent session detected on different HWID
HWID mismatch. Reset required.Key bound to different HWID
Key banned due to suspicious activity.10 HWID mismatches triggered auto-ban
Invalid app secretWrong app_secret (only when app_secret is sent)
This app is currently disabledApp was disabled from the admin panel
POST /api/heartbeat

Keeps the session alive. Must be called every 30 seconds after a successful authentication. Sessions expire after 90 seconds without a heartbeat.

POSThttp://your-server.com:4000/api/heartbeat
Request Body (JSON)
FieldTypeDescription
tokenstringThe session token returned by /api/authenticate
Response
{ "success": true }   // session still valid
{ "success": false }  // session expired or key banned β†’ EXIT APP
Rule: If heartbeat returns success: false for any reason, you must immediately close/exit the protected application. This is how the admin can remotely terminate sessions by banning a key.
C++ Integration

Uses WinINet for HTTP β€” no third-party libraries needed. Works on Windows only.

Setup

Add to your linker or at the top of your file:

#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "advapi32.lib")
#include <windows.h>
#include <wininet.h>
#include <string>
Configuration
#define SERVER_HOST  "your-server.com"
#define SERVER_PORT  4000
#define APP_SECRET   "ca_your_secret_here"
HTTP POST helper
std::string HttpPost(const char* path, const std::string& body) {
    HINTERNET hInet = InternetOpenA("AuthClient", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    HINTERNET hConn = InternetConnectA(hInet, SERVER_HOST, SERVER_PORT,
                                       NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
    HINTERNET hReq  = HttpOpenRequestA(hConn, "POST", path, NULL, NULL, NULL, 0, 0);
    const char* ct  = "Content-Type: application/json\r\n";
    HttpSendRequestA(hReq, ct, (DWORD)strlen(ct), (LPVOID)body.c_str(), (DWORD)body.size());
    std::string resp; char buf[4096]; DWORD read;
    while (InternetReadFile(hReq, buf, sizeof(buf)-1, &read) && read)
        { buf[read]=0; resp+=buf; }
    InternetCloseHandle(hReq); InternetCloseHandle(hConn); InternetCloseHandle(hInet);
    return resp;
}

std::string ParseField(const std::string& json, const std::string& key) {
    std::string search = "\"" + key + "\":\"";
    size_t s = json.find(search);
    if (s == std::string::npos) return "";
    s += search.size();
    size_t e = json.find('"', s);
    return (e != std::string::npos) ? json.substr(s, e - s) : "";
}
Authenticate function
std::string g_token;

bool Authenticate(const std::string& key, const std::string& hwid,
                  std::string& outMsg, std::string& outExpiry) {
    std::string body = "{\"key\":\"" + key + "\",\"hwid\":\"" + hwid +
                       "\",\"app_secret\":\"" APP_SECRET "\"}";
    std::string resp = HttpPost("/api/authenticate", body);
    if (resp.find("\"success\":true") == std::string::npos) {
        outMsg = ParseField(resp, "message");
        return false;
    }
    g_token   = ParseField(resp, "token");
    outExpiry = ParseField(resp, "expiry");
    return true;
}

// Heartbeat thread β€” keeps session alive
DWORD WINAPI HeartbeatWorker(LPVOID) {
    while (true) {
        Sleep(30000);
        std::string body = "{\"token\":\"" + g_token + "\"}";
        std::string resp = HttpPost("/api/heartbeat", body);
        if (resp.find("\"success\":true") == std::string::npos)
            ExitProcess(1); // session expired or key banned
    }
    return 0;
}

// Usage in main():
// std::string msg, expiry;
// if (!Authenticate(userKey, hwid, msg, expiry)) { /* show error */ return 1; }
// CreateThread(NULL, 0, HeartbeatWorker, NULL, 0, NULL);
// // continue with your app...
C# Integration

Uses HttpClient (built into .NET 4.5+). No NuGet packages needed.

Configuration
const string SERVER = "http://your-server.com:4000";
const string APP_SECRET = "ca_your_secret_here";
Authentication
using System;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Text.Json;

static HttpClient http = new HttpClient();
static string sessionToken = "";

static async Task<bool> Authenticate(string key, string hwid) {
    var payload = JsonSerializer.Serialize(new {
        key, hwid, app_secret = APP_SECRET
    });
    var content = new StringContent(payload, Encoding.UTF8, "application/json");
    var resp = await http.PostAsync(SERVER + "/api/authenticate", content);
    var json = await resp.Content.ReadAsStringAsync();
    using var doc = JsonDocument.Parse(json);
    var root = doc.RootElement;
    if (root.GetProperty("success").GetBoolean()) {
        sessionToken = root.GetProperty("token").GetString();
        Console.WriteLine("Expires: " + root.GetProperty("expiry").GetString());
        return true;
    }
    Console.WriteLine("Auth failed: " + root.GetProperty("message").GetString());
    return false;
}

static async void StartHeartbeat() {
    while (true) {
        await Task.Delay(30000);
        var payload = JsonSerializer.Serialize(new { token = sessionToken });
        var content = new StringContent(payload, Encoding.UTF8, "application/json");
        var resp = await http.PostAsync(SERVER + "/api/heartbeat", content);
        var json = await resp.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);
        if (!doc.RootElement.GetProperty("success").GetBoolean())
            Environment.Exit(1); // session expired
    }
}

// Usage:
// if (!await Authenticate(userKey, hwid)) return;
// StartHeartbeat();
// // continue with your app...
Python Integration

Uses the requests library. Install with: pip install requests

Configuration
SERVER = "http://your-server.com:4000"
APP_SECRET = "ca_your_secret_here"
Full integration
import requests
import threading
import sys
import time

SERVER = "http://your-server.com:4000"
APP_SECRET = "ca_your_secret_here"
session_token = None

def authenticate(key: str, hwid: str) -> bool:
    global session_token
    resp = requests.post(f"{SERVER}/api/authenticate", json={
        "key": key,
        "hwid": hwid,
        "app_secret": APP_SECRET
    }).json()
    if resp.get("success"):
        session_token = resp["token"]
        print(f"Authenticated. Expires: {resp['expiry']}")
        return True
    print(f"Auth failed: {resp.get('message')}")
    return False

def heartbeat_loop():
    while True:
        time.sleep(30)
        try:
            resp = requests.post(f"{SERVER}/api/heartbeat",
                                 json={"token": session_token}).json()
            if not resp.get("success"):
                print("Session expired. Exiting.")
                sys.exit(1)
        except Exception:
            sys.exit(1)

# Usage:
# hwid = get_hwid()  # implement your own HWID function
# if not authenticate(user_key, hwid):
#     sys.exit(1)
# t = threading.Thread(target=heartbeat_loop, daemon=True)
# t.start()
# # continue with your app...
HWID System

HWID (Hardware ID) is what binds a license key to a specific machine. On first authentication the HWID gets saved to the key β€” afterwards only that machine can use it.

Recommended approach (C++)

Store a randomly generated UUID in a hidden system file. This survives reboots and HWID spoofers, but is lost on Windows reinstall (handled by RST HWID in admin panel).

#define HWID_PATH "C:\\Windows\\System32\\spool\\drivers\\color\\wdc_sys.dat"

std::string GenerateUID() {
    BYTE buf[16]; CryptGenRandom(CryptProvHdl, 16, buf); // get random bytes
    char hex[33]; hex[32] = 0;
    for (int i = 0; i < 16; i++) sprintf_s(hex+2*i, 3, "%02X", buf[i]);
    return std::string(hex);
}

std::string GetHWID() {
    HANDLE f = CreateFileA(HWID_PATH, GENERIC_READ, 0, NULL,
                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (f != INVALID_HANDLE_VALUE) {
        char buf[64] = {}; DWORD read;
        ReadFile(f, buf, 63, &read, NULL); CloseHandle(f);
        if (read == 32) return std::string(buf, 32);
    }
    // File missing β€” generate new ID
    std::string uid = GenerateUID();
    HANDLE g = CreateFileA(HWID_PATH, GENERIC_WRITE, 0, NULL,
                           CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM, NULL);
    if (g != INVALID_HANDLE_VALUE) {
        DWORD w; WriteFile(g, uid.c_str(), 32, &w, NULL); CloseHandle(g);
    }
    return uid;
}
When to RST HWID

Use the RST HWID button in the admin panel when a legitimate user reinstalls Windows and their HWID file is lost. This clears the bound HWID so the key can re-activate on the new installation.

Note: The hidden file approach is resistant to most HWID spoofer tools. If a user spoof-changes their reported hardware IDs, the file still contains the original generated ID.
Auto-Update System

Each app can have a version number and a linked .exe file. When your .exe starts, it checks the server for the latest version. If a newer version is available, it downloads and replaces itself automatically.

Setup in Admin Panel
1
Upload your .exe to the serverPlace your compiled .exe file inside the public/files/ folder on your Pterodactyl server.
2
Set the version on the App cardIn Apps section, click ⬆ Set Version on your app. Enter the version (e.g. 1.0.5) and the exact file name (e.g. cheese-aimbot.exe).
3
Hardcode the current version in your .exeEach build of your .exe has a hardcoded version string. At startup it checks if the server version is newer.
GET /api/version
GEThttp://your-server.com:4000/api/version?app_secret=ca_xxx
Response
{
  "success": true,
  "version": "1.0.5",
  "download_url": "http://your-server.com:4000/files/cheese-aimbot.exe"
}
Logic: If version from server != your hardcoded version β†’ download download_url β†’ replace current .exe β†’ restart.
C++ Auto-Update Example
#define APP_VERSION  "1.0.3"   // hardcoded in this build
#define APP_SECRET   "ca_your_secret_here"
#define SERVER_HOST  "your-server.com"
#define SERVER_PORT  4000

// Download file from URL to a local path using WinINet
bool DownloadFile(const std::string& url, const std::string& destPath) {
    // Parse host and path from URL
    // (simplified β€” assumes http://host:port/path format)
    size_t hostStart = url.find("//") + 2;
    size_t portPos   = url.find(':', hostStart);
    size_t pathStart = url.find('/', portPos);
    std::string host = url.substr(hostStart, portPos - hostStart);
    int    port      = std::stoi(url.substr(portPos + 1, pathStart - portPos - 1));
    std::string path = url.substr(pathStart);

    HINTERNET hInet = InternetOpenA("Updater", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    HINTERNET hConn = InternetConnectA(hInet, host.c_str(), (INTERNET_PORT)port,
                                       NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
    HINTERNET hReq  = HttpOpenRequestA(hConn, "GET", path.c_str(), NULL, NULL, NULL, 0, 0);
    if (!HttpSendRequestA(hReq, NULL, 0, NULL, 0)) { InternetCloseHandle(hReq); return false; }

    HANDLE hFile = CreateFileA(destPath.c_str(), GENERIC_WRITE, 0, NULL,
                               CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) return false;

    char buf[8192]; DWORD read;
    while (InternetReadFile(hReq, buf, sizeof(buf), &read) && read) {
        DWORD written; WriteFile(hFile, buf, read, &written, NULL);
    }
    CloseHandle(hFile);
    InternetCloseHandle(hReq); InternetCloseHandle(hConn); InternetCloseHandle(hInet);
    return true;
}

// Check version and auto-update if needed
// Returns false if update failed (caller should exit)
bool CheckAndUpdate() {
    std::string url = "http://" SERVER_HOST ":" + std::to_string(SERVER_PORT) +
                      "/api/version?app_secret=" APP_SECRET;

    // GET request (reuse HttpPost logic with GET method, or make a simple version):
    HINTERNET hInet = InternetOpenA("Updater", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    HINTERNET hUrl  = InternetOpenUrlA(hInet, url.c_str(), NULL, 0, 0, 0);
    if (!hUrl) { InternetCloseHandle(hInet); return true; } // skip update on network error
    std::string resp; char buf[4096]; DWORD read;
    while (InternetReadFile(hUrl, buf, sizeof(buf)-1, &read) && read) { buf[read]=0; resp+=buf; }
    InternetCloseHandle(hUrl); InternetCloseHandle(hInet);

    std::string serverVer = ParseField(resp, "version");
    std::string dlUrl     = ParseField(resp, "download_url");

    if (serverVer.empty() || serverVer == APP_VERSION) return true; // up to date

    // Newer version available β€” download to temp file then replace self
    char selfPath[MAX_PATH], tempPath[MAX_PATH];
    GetModuleFileNameA(NULL, selfPath, MAX_PATH);
    std::string tempFile = std::string(selfPath) + ".old";

    if (!DownloadFile(dlUrl, tempPath)) return true; // download failed, skip

    // Schedule self-replace via cmd: rename old β†’ .old, new β†’ original name, restart
    std::string cmd =
        "cmd /c ping 127.0.0.1 -n 3 >nul"
        " & move /y \"" + std::string(tempPath) + "\" \"" + std::string(selfPath) + "\""
        " & start \"\" \"" + std::string(selfPath) + "\"";
    ShellExecuteA(NULL, "open", "cmd.exe",
                  ("/c " + cmd).c_str(), NULL, SW_HIDE);
    ExitProcess(0); // exit current instance
    return true;
}

// In main(), call BEFORE authenticate:
// CheckAndUpdate();
// ... then authenticate as normal
How to deploy an update: (1) Compile new .exe with updated APP_VERSION. (2) Upload .exe to public/files/ on the server. (3) In admin panel, click ⬆ Set Version on the app and enter the new version number. All users will auto-update on next launch.
Universal Launcher

The Universal Launcher is a single CheeseLauncher.exe that works for ALL your products. It authenticates the user's key, automatically downloads the correct product exe, runs it from a hidden temp folder, and keeps the session alive via heartbeat β€” all without any code changes to your products.

How it works
1
User opens CheeseLauncher.exeThe only file the user ever needs. No other exe required in the folder.
2
Product selector dropdownIf the user has previously authenticated products, they appear in a dropdown. Selecting one auto-fills the saved key. They can also choose "οΌ‹ Enter new key" to add a new product.
3
User enters key β†’ LAUNCHLauncher sends key + HWID to the server. No app_secret needed β€” the server automatically finds which app the key belongs to.
4
Server responds with token + download_url + versionThe download_url and version come from the app's Set Version configuration in the admin panel.
5
Smart cache checkIf the cached version matches the server version β†’ runs immediately from cache. If different β†’ downloads the new exe, deletes the old one, updates cache. No unnecessary downloads.
6
Launcher hides to system trayA πŸ§€ icon appears in the tray. Heartbeat runs every 30s. When the product closes, launcher auto-exits.
Setup checklist (per product)
StepWhere
1. Create App in admin panelApps section β†’ + New App
2. Generate keys assigned to that AppGenerate section β†’ select App from dropdown
3. Upload your product .exe to the serverPterodactyl β†’ public/files/
4. Set Version in admin panelApps β†’ ⬆ Set Version β†’ enter filename + version
5. Distribute CheeseLauncher.exe to usersOne universal launcher for all products
Updating a product
Only 3 steps every time you update:

1. Compile the new .exe (same filename always)
2. Upload to public/files/ on Pterodactyl (replaces the old one)
3. In admin panel β†’ Apps β†’ ⬆ Set Version β†’ change version number

Users get the update automatically the next time they open the launcher.
Multi-product support

A user can have keys for multiple apps. The launcher saves each key separately and shows them all in the product dropdown.

// Example: user has 3 products
SELECT PRODUCT β–Ό
  CheeseEsp         ← key auto-filled, cached exe ready
  CheeseAimbot      ← key auto-filled, cached exe ready
  CheeseMisc        ← key auto-filled, cached exe ready
  οΌ‹ Enter new key  ← add another product
What the user sees in their folder
πŸ“ Any folder/
└── CheeseLauncher.exe   ← the only file the user ever needs

// Hidden in %APPDATA%\CheeseAuth\ (users never see this):
//   keys\CheeseEsp.key       ← saved key (DPAPI encrypted)
//   keys\CheeseAimbot.key    ← saved key (DPAPI encrypted)
//   cache\CheeseEsp\         ← cached exe + version file
//   cache\CheeseAimbot\      ← cached exe + version file
C# configuration (before compiling)

Before compiling the launcher, change these two lines in Program.cs:

// ← Change SERVER to your actual domain or IP
private const string SERVER      = "http://cheese-auth.duckdns.org:4000";
private const string APP_VERSION = "1.0.0";  // version of the launcher itself
⚠ Important: SERVER must point to your CheeseAuth server URL.
No app_secret needed β€” the server automatically finds which product the key belongs to and returns the correct download_url.

Compile once β†’ works for all your products. The only thing that changes per product is what you configure in the admin panel (App name, file name, version).
What the product exe needs
Zero code required in the product exe.

The product exe is just a normal program. It does not need any CheeseAuth code, API calls, or configuration. CheeseAuth.exe handles everything on its behalf.
What CheeseAuth.exe handles automatically
TaskWho handles it
HWID generation & bindingCheeseAuth.exe
Key validationCheeseAuth.exe
Session tokenCheeseAuth.exe
Heartbeat every 30sCheeseAuth.exe
Auto-update & downloadCheeseAuth.exe
Kill product exe if key is bannedCheeseAuth.exe
What happens when a key is banned remotely
Admin bans key in panel
        ↓
Next heartbeat (max 30 seconds later) β†’ server returns success: false
        ↓
CheeseAuth.exe kills the product exe (Process.Kill)
        ↓
CheeseAuth.exe exits

// The product exe is terminated automatically.
// The developer does not need to implement anything for this to work.
For developers integrating their product

If someone wants to use the Universal Launcher with their product, they only need:

WhatWhere to get it
CheeseAuth.exe (compiled launcher)You provide this
App created in the admin panelYou create it, assign their exe as file_name
License key assigned to their AppYou generate it from the panel
Their product exe requires zero changes. They just upload it to public/files/ and you configure it in Set Version.
Error Reference

All API endpoints return JSON. On error, success is false and message contains the reason.

HTTP Status Codes
CodeMeaning
200Request processed β€” check success field for result
400Missing required fields or invalid parameters
403Key banned, HWID mismatch, concurrent session, invalid app_secret, or app disabled
404License key not found in database
429Rate limit exceeded β€” IP (10/15min) or per-key (5 failures/10min)
500Server-side database error
All possible error messages
MessageHTTPReason
Missing key or HWID400Request body missing required fields
Invalid license key404Key does not exist in the database
This key has been banned403Key manually banned or auto-banned
Too many failed attempts for this key. Try again later.429Per-key rate limit: 5 HWID failures in 10 min
Key is already active on another device.403Concurrent session detected on a different HWID
HWID mismatch. Reset required.403Key is bound to a different HWID
Key banned due to suspicious activity.40310 HWID mismatches triggered auto-ban
License expired403Key is past its expiry date
Invalid app secret403Wrong app_secret sent (C++ integration mode)
This app is currently disabled403App was disabled from the admin panel
Too many attempts. Try again in X minute(s).429IP rate limit: 10 attempts per 15 min
Database error500Server-side database failure
Anti-abuse limits
RuleLimitResult
IP login attempts10 per 15 minutesIP temporarily blocked (429)
HWID mismatch per key5 per 10 minutesKey temporarily blocked (429)
HWID mismatch per key10 totalKey auto-banned permanently (403)
Session without heartbeat90 secondsSession expired, must re-authenticate
Heartbeat intervalEvery 30 secondsSession stays alive
Apps / Products
Loading…
Generate New Key
App ↕ License Key ↕ Type ↕ Status ↕ HWID Note ↕ Created ↓ Expires ↕ Last Login ↕ Actions
Loading…