Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.authforge.cc/llms.txt

Use this file to discover all available pages before exploring further.

If you’ve ever wondered “is this signed-response stuff actually real or marketing?”, the curl examples below are your answer. The Node verification snippet at the bottom proves a real Ed25519 signature against the public key on this very page - no account, no SDK, no trust. The credentials hit a public sandbox app on the live auth.authforge.cc endpoint. Every request goes through the production code path - real Ed25519 signing, real nonce replay protection, real rate limits - and returns a payload that’s byte-for-byte indistinguishable from one a paying customer would get. The only difference is that this app’s traffic doesn’t get billed.
Public credentials, deliberately. Do not use them for anything important - rate limits are tighter, the daily request ceiling is shared with every other reviewer, and the license can be reset or revoked at any time. When you’re ready to ship, sign up for free starter credits and your own keys.

Demo credentials

FieldValue
Application ID789ffbc6-555c-4a3c-8e9b-89be6f865195
Application secretdemo-key-1a73-471e-ad08-8bd159b7e69c
Application Ed25519 public keyXRvq8r6PnKqJuNNBrNezhwPmZs887l1Fu4U/WGPz8Wc=
Demo license keyHE6H-S2V5-PKMW-WDVD
The license is unlimited-seat (what does that mean?) - any HWID can authenticate against it, so it doesn’t matter whether you’ve already used it on another machine or which fingerprint your shell sends.

1. Validate

This is the first call every SDK makes. It binds your HWID (or in this case doesn’t, since the key is unlimited-seat), debits one credit (skipped on the demo), and returns a signed session token plus the entitlements you’d want to render in your UI.
curl -sS -X POST https://auth.authforge.cc/auth/validate \
  -H "Content-Type: application/json" \
  -d '{
    "appId":      "789ffbc6-555c-4a3c-8e9b-89be6f865195",
    "appSecret":  "demo-key-1a73-471e-ad08-8bd159b7e69c",
    "licenseKey": "HE6H-S2V5-PKMW-WDVD",
    "hwid":       "demo-machine-001",
    "nonce":      "your-random-nonce-12345678"
  }'
A successful response has this shape:
FieldTypeNotes
statusstring"success" on the happy path; "failed" with an error code otherwise.
payloadstringBase64-encoded JSON. Decode it to read the entitlements (see fields below).
signaturestringBase64 Ed25519 signature, always 88 chars. Verify it against the app’s public key before trusting payload.
keyIdstringIdentifier for the signing key. Useful when rotating keys: clients can pin multiple trusted keys and use keyId to pick the right verifier.
After base64-decoding payload you’ll see fields like sessionToken, expiresIn, licenseExpiresAt, unlimitedHwidSlots: true, and any application/license variables set on the app. The full field list is in Concepts → Signed payload fields. We deliberately don’t print a fake payload/signature example here - hand-crafted strings can’t actually be verified, so they’d just be lying decoration. Run the curl above and you’ll have a real one to feed into the Node snippet below.

2. Heartbeat

After validate, your SDK keeps the session alive by calling /auth/heartbeat periodically with the sessionToken from the previous response. Heartbeats refresh the token and are how the server delivers instant revocations to running clients.
curl -sS -X POST https://auth.authforge.cc/auth/heartbeat \
  -H "Content-Type: application/json" \
  -d '{
    "appId":        "789ffbc6-555c-4a3c-8e9b-89be6f865195",
    "sessionToken": "PASTE_SESSION_TOKEN_FROM_VALIDATE_RESPONSE",
    "hwid":         "demo-machine-001"
  }'
Response is the same shape as validate: { status, payload, signature, keyId }. The new payload carries a fresh sessionToken and expiresIn.

3. Self-ban

Lets a client self-report cheating / tampering / EULA violations and ask the server to revoke or blacklist itself. Useful for anti-tamper integrations - the server is the source of truth and other SDKs on this license stop working immediately.
The demo app neutralizes selfban. This call below is intentionally a no-op: it exists so you can see the request and response shape, the auth + rate-limit pipeline, and confirm that the endpoint accepts your payload. It does not revoke the demo license or blacklist anything, no matter what flags you send. If it did, the first reviewer would lock everyone else out of the demo. In your own app, the same handler with the same flags does revoke the license and persist the blacklist - that’s the whole point of the endpoint.
curl -sS -X POST https://auth.authforge.cc/auth/selfban \
  -H "Content-Type: application/json" \
  -d '{
    "appId":         "789ffbc6-555c-4a3c-8e9b-89be6f865195",
    "appSecret":     "demo-key-1a73-471e-ad08-8bd159b7e69c",
    "licenseKey":    "HE6H-S2V5-PKMW-WDVD",
    "hwid":          "demo-machine-001",
    "nonce":         "another-random-nonce-87654321",
    "blacklistHwid": false,
    "blacklistIp":   false
  }'
Response:
{
  "status": "success",
  "mode": "pre_session",
  "performed": { "revokeLicense": false, "blacklistHwid": false, "blacklistIp": false }
}
The performed object is the server’s honest report of what it actually did - on the demo app, it’s always all-false. In production it reflects the request flags (defaulting to true post-session, false for revokeLicense pre-session).

Verify the signed payload (Node)

This is the part that matters. The whole point of “signed validation” is that you can verify the response server-side without trusting the network or any TLS-terminating intermediary. Drop this into a .mjs file and run it after the validate call above:
import { verify, createPublicKey } from "node:crypto";

// The Ed25519 public key from this docs page (raw 32-byte key, base64).
const PUBLIC_KEY_B64 = "XRvq8r6PnKqJuNNBrNezhwPmZs887l1Fu4U/WGPz8Wc=";

const publicKey = createPublicKey({
  // Wrap raw Ed25519 bytes in a SPKI DER prefix so Node accepts them.
  key: Buffer.concat([
    Buffer.from("302a300506032b6570032100", "hex"),
    Buffer.from(PUBLIC_KEY_B64, "base64")
  ]),
  format: "der",
  type: "spki"
});

// Paste the validate response here (or fetch() it inline).
const response = {
  status: "success",
  payload:   "PASTE_PAYLOAD_FROM_VALIDATE_RESPONSE",
  signature: "PASTE_SIGNATURE_FROM_VALIDATE_RESPONSE",
  keyId:     "PASTE_KEY_ID"
};

// IMPORTANT: `response.payload` is used in two different ways below, and
// that's deliberate - not a bug. The same base64 string serves two purposes:
//
//   1. As the input to `verify()`, treated as UTF-8 text. The server signs
//      the base64-encoded string itself (its UTF-8 bytes), NOT the raw JSON
//      bytes underneath. This avoids any canonicalization ambiguity over the
//      JSON representation.
//   2. As the encoded entitlement JSON. Decode the same string from base64
//      to recover the JSON bytes, then `JSON.parse` them.
//
// All six AuthForge SDKs (Node, Python, Go, Rust, C#, C++) verify exactly
// this way. If you change either line to "match" the other, verification
// will silently fail.
const ok = verify(
  null,
  Buffer.from(response.payload, "utf8"),     // [1] verify the base64 string as UTF-8 bytes
  publicKey,
  Buffer.from(response.signature, "base64")
);

if (!ok) throw new Error("signature verification FAILED");

const payload = JSON.parse(
  Buffer.from(response.payload, "base64").toString("utf8") // [2] decode same string from base64 → JSON
);
console.log("verified payload:", payload);
If verify(...) returns true, you’ve confirmed end-to-end that:
  1. The payload you received was signed by the private key paired with the public key in this docs page.
  2. Nobody between AuthForge and your machine modified a single byte of the JSON.
  3. The entitlement fields inside payload (license expiry, variables, unlimitedHwidSlots, etc.) can be trusted as offline-verifiable claims.
That’s the property all six AuthForge SDKs rely on internally. You’ve just reproduced it in 20 lines of Node.

Now make your own keys

You just verified a real Ed25519 signature against a real production endpoint. The exact same code, with your own appId / appSecret / public key, is your entire integration. Sign up for free, get starter credits, and ship - no credit card, takes about a minute.

Limits

The demo has a hard guardrail at every layer. Reviewers running the curl examples in parallel during a traffic spike will sometimes hit one - that’s the design, not a bug.
LimitValueError returned
Per-IP rate (validate)20 req/minrate_limited
Per-IP rate (heartbeat)20 req/minrate_limited
Per-IP rate (selfban)20 req/minrate_limited
Per-license rate (validate)60 req/minrate_limited
Per-license rate (selfban)30 req/minrate_limited
Per-app daily ceiling50,000 req/daydemo_quota_exceeded
rate_limited resets continuously (token-bucket refill); demo_quota_exceeded resets at 00:00 UTC. Both come back with HTTP 429. Your own apps have looser per-IP / per-license limits and no daily ceiling - they’re billed per request instead.

Ready for the real thing?

Create an account - it’s free, comes with starter credits, and gets you your own appId/appSecret plus unlimited license generation. The exact same code you wrote against the demo will work against your own credentials with a one-line swap.