Skip to main content

Requirements

  • Node.js 18.0 or later
  • No runtime dependencies beyond Node.js built-ins

Installation

Install from npm as @authforgecc/sdk:
The package ships authforge.mjs and TypeScript declarations (authforge.d.ts).
Prefer a single file in-repo? Copy authforge.mjs from GitHub. The npm package is recommended for versioning and updates.

TypeScript

Use the same import as JavaScript; types resolve from the package:

Quick start

Constructor parameters

ttlSeconds (grace period)

Requested session token lifetime in seconds for /auth/validate. This is the grace period: how long the app keeps running on the signed session without contacting AuthForge. Pass null (or omit) to accept the server default of 24 hours. The server clamps to [3600, 604800] (1 hour to 7 days). The requested TTL is preserved across heartbeat refreshes, and apps relying on the grace period alone can stretch their window up to 7 days.

Billing

  • Each successful login() or validateLicense() costs 1 credit (one /auth/validate debit).
  • The grace period is free: local session re-verification makes no network calls and consumes no credits.
  • Online check-ins (if enabled) cost 1 credit per 10 successful heartbeats (billed on every 10th call).
  • With online check-ins, revocations take effect on the next check-in regardless of interval; without them, at session expiry.
  • A new self-serve offline activation (activateOffline) costs 2 credits; activating the same machine again is free.

Login

Returns Promise<boolean>. It resolves to true if authentication succeeded, false otherwise. On success, the SDK starts a background timer that re-verifies the signed session locally during the grace period (and sends online check-ins if onlineHeartbeat: true).

Validate license (no session)

Same /auth/validate request and Ed25519 verification as login, without mutating the client’s stored session or starting the background timer. Use this for bots and repeated checks; use login when you want a long-lived session. Successful results may also include sessionExpiresAt, licenseExpiresAt (null for lifetime keys), maxHwidSlots, hwidCount, and licenseLabel when present in the signed payload; the decoded payload is always available as sessionData.

Failure callback

If authentication fails, an online check-in fails, or the session expires, the SDK calls your onFailure callback. Returning normally from the callback keeps the process running; if the callback throws, the SDK calls process.exit(1). Without a callback (1.4.1+), a transient background check failure writes AuthForge: background check failed (<code>); retrying next interval to stderr and check-ins continue; every other failure (a rejected login(), a definitive check-in answer, the grace period running out) calls process.exit(1).

Check-in failures (1.4.0+)

Background check failures (online check-ins and the end of the grace period) reach onFailure("heartbeat_failed", error), where error is an AuthForgeError:
  • error.code: the server’s error code from the response body, whatever the HTTP status (revoked, expired, hwid_mismatch, blocked, session_expired, rate_limited, no_credits, system_error, malformed_request, …; codes this SDK version doesn’t know are passed through unchanged), or an SDK code: network_error, timeout, http_error_<status> (non-JSON error body), unexpected_response, signature_mismatch, nonce_mismatch. error.message is the code for server errors, url_error: ... for network failures.
  • error.transient / error.fatal: the classification. isTransientError(errorOrCode) is the same check as a function; definitiveErrorCodes is the allowlist of fatal codes and transientErrorCodes lists the named transient codes for reference.
A failed check-in only counts as an AuthForge verdict when its body is {"status": "failed", "error": "<code>"}. Any other failure body (for example {"error": "revoked"} with no status, typically from a proxy or captive portal) is reported as unexpected_response, with the raw status/error in error.message. Request retries are automatic: rate_limited (or HTTP 429 with no error code) is retried after 2s, then 5s; no_credits, demo_quota_exceeded, and app_burn_cap_reached are never retried immediately and wait for the next interval; a network failure is retried once after 2s. Network failures are reported once, with code network_error or timeout: as heartbeat_failed from check-ins and, since 2.0.0, as login_failed from login() (never as a separate network_error reason). selfBan() and activateOffline() reject with them as AuthForgeError instead and never call onFailure. onFailure may safely call logout(), isAuthenticated(), or login(). A check-in that is still in flight when you call logout() or login() is discarded, so a late response never brings the old session back. To tolerate short outages but shut down on a definitive answer, have the callback signal your app and let it save and exit:
You can also pass licenseLost.signal to APIs that accept an AbortSignal (fetch, setTimeout from node:timers/promises, events.once, …) so in-flight work stops with it. Calling process.exit(1) inside onFailure is a last resort: it ends the process without waiting for pending writes or finally blocks, so save the user’s work first.
If you don’t set onFailure, a rejected login or a definitive background failure (including session_expired once the grace period runs out) terminates the process immediately with process.exit(1), without waiting for pending writes. Transient check-in failures only print a warning to stderr. Set a callback in production for graceful shutdown.

Reading variables

After successful login, app variables and license variables are available on the client:

Grace period and online check-ins

By default, the app runs through the grace period after activation: the SDK re-verifies the signed session locally, makes no network calls, and stops when the session expires. Opt in to online check-ins for fast revocation and concurrent-use detection:
See Online Check-ins for a detailed comparison.

Offline license files (.authforge)

For machines that never connect to the internet, the operator mints a signed offline license file in the dashboard or Developer API (1 credit). The SDK verifies it locally with your public key and the machine HWID; no network, no check-ins, and online login() is untouched. Omit appSecret so the air-gapped binary does not contain the App Secret.
Failures call onFailure("offline_login_failed", new Error(code)) and return false (never process.exit). Codes, in check order: bad_armor, bad_signature, unsupported_version, malformed_payload, wrong_app, expired, hwid_mismatch. client.verifyLicenseFile(pathOrText) and the exported verifyLicenseFile({ file, appId, publicKey, hwid }) run the same checks without touching state. Issued files cannot be revoked remotely; they stay valid until their own expiry. createActivationRequest() / writeActivationRequest(path) produce the .authforge-request the operator uploads; hostname is omitted unless includeMachineName: true. See Activation requests.

Migrating from heartbeatMode

heartbeatMode: "LOCAL" / "SERVER" is deprecated but still accepted:
  • heartbeatMode: "LOCAL": remove the option; the grace period is now the default.
  • heartbeatMode: "SERVER": use onlineHeartbeat: true instead.

Self-serve offline activation

await client.activateOffline(licenseKey, { path }) turns a license key into a lifetime .authforge file bound to this machine, with one online call to POST /auth/offline/activate. After that the app starts with loginFromFile() and never needs the network. Use it for one-time-purchase apps that should run offline forever. See Self-serve activation and the walkthrough Sell offline apps.
  • Needs the App Secret. Pass appSecret to the constructor (air-gapped builds omit it). A missing secret or empty key rejects with a plain Error before any request.
  • Your app must opt in (off by default), and the license must be perpetual and seat-limited.
  • The SDK sends the same HWID as login, verifies the returned file (signature, app id, this machine’s HWID) before saving it, writes it to the path atomically (creating parent folders), and leaves the client in an offline session exactly as after loginFromFile(): variables available, no grace-period timer, no check-ins.
  • Billing: 2 credits per new machine (one validation plus one mint), charged to you. The same machine activating again gets the identical file back for free. Every refusal is free.
  • A license can activate at most maxHwidSlots distinct machines, ever. HWID resets do not free machine slots.
  • A refund or chargeback revokes the license online but cannot reach files already issued. For a lifetime file, that means the buyer keeps the app.
Failures reject with AuthForgeError: error.code is the code and error.fatal / error.transient the classification. The codes are the server’s (offline_activation_disabled, offline_activation_requires_perpetual, offline_activation_requires_seats, offline_activation_limit_reached, hwid_mismatch when no seat is free, invalid_key, revoked, no_credits, rate_limited, …) or the SDK’s: offline_file_rejected (fatal; the server’s file failed local verification, reason in the message) and file_write_failed (transient; the file could not be written, and calling again returns the same file for free). The method never calls onFailure and never exits the process; nothing is written on failure and any existing session is left alone. On offline_activation_limit_reached, tell the user to contact you. Messages to show: Self-serve activation errors. loginFromFile can report a missing or rejected file to onFailure as offline_login_failed. If you set a callback, return normally for that reason so the app reaches the key prompt.

Full example

GitHub

Full source, changelog, and issues: AuthForgeCC/authforge-node