Skip to main content
An offline license file is a signed document (.authforge) that you mint in the AuthForge cloud and hand to a customer whose machine never connects to the internet. The SDK on that machine verifies the file with only your app public key, app id, and its own HWID. It never calls /auth/validate, never checks in, and never needs AuthForge network access for the life of the file. Do not embed the App Secret in air-gapped binaries: loginFromFile does not use it. Pass an empty secret (or omit it in languages that allow that) when constructing an offline-only client.
This is a separate mode from the default. Most apps should keep using online activation + the grace period. Reach for offline files only when a machine genuinely cannot phone home (factory floors, classified networks, ships, embedded appliances).

Offline file vs grace period

The grace period is session continuation: it keeps an app running for a while after an online activation. An offline file is persistent air-gap licensing: a standalone credential the machine can verify forever without you.

Revocation: read this before you mint

Revoking a license online blocks new mints and stops online activations. It does not reach files that are already on customer machines. Every issued .authforge file stays valid until its own expiresAt (or forever, for lifetime files). There is no remote kill switch, and we do not pretend otherwise. Practical consequences:
  • Prefer short expiries (30 to 90 days) and re-issue on a schedule. Treat re-issuing as your revocation lever.
  • Prefer HWID-bound files. An unbound (any) file works on every machine that has a copy.
  • Mint lifetime files only for perpetual licenses where you accept that the entitlement is permanent.
  • Keep mint history (the dashboard shows it per license) so you know exactly which files exist and when they lapse.

Minting a file

Dashboard

  1. Open the license (Applications -> app -> Licenses -> license page) and click Mint .authforge file.
  2. Pick an expiry. Presets are 30 days, 90 days, 1 year, the license expiry, or Lifetime (perpetual licenses only). The file can never outlive the license.
  3. Pick a machine binding. Bound to specific HWIDs (recommended) prefills the HWIDs already bound to the license. Prefer an activation request (.authforge-request) over a pasted HWID: the dashboard checksums the file so email damage becomes a clear error instead of a silent hwid_mismatch. You can still paste the getter (getHwid() / get_hwid() / HWID() / hwid() / GetHwid()). Any machine requires an explicit acknowledgement.
    Collect the HWID from the same SDK (and language) that will call loginFromFile. HWID fingerprints are not portable across SDKs: a machine reports a different value from the Node SDK than from the C# SDK. A file bound to the wrong one fails with hwid_mismatch. If your product uses several SDKs on the same box, pass hwidOverride with an identifier you control instead.
  4. Click Mint & download. The browser downloads <licenseKey>.authforge and 1 credit is charged. The file body is never stored server-side, only its SHA-256, so download it now.
The row menu on the licenses table has the same action, and the license page lists every mint (issued, expires, binding, source, file id).

Developer API

Returns 201 with { file, fileName, meta }. file is the armored text; write it to fileName. Scope write:licenses. See Licenses API -> Offline license files for the full reference and error codes.

Mint rules

  • License must be active and not expired; the app must not be paused.
  • expiresAt must be in the future and on or before the license expiry. null (lifetime) is only accepted for licenses without an expiry.
  • hwidMode: "bound" needs 1 to 16 HWIDs. hwidMode: "any" needs allowUnbound: true.
  • Rate limited: 30 mints per minute per account, 10 per minute per license.
  • Billing: 1 credit per successful mint, debited through the same path as /auth/validate (so your app’s hourly/daily burn caps apply). Rejected mints are free. A mint fails with no_credits before anything is signed.
  • Every mint is audited (actor, time, license, expiry, HWID policy, file id and hash).

Verifying on the air-gapped machine

Every official SDK (1.2.0+) ships two things:
  • verifyLicenseFile(...): a pure function that checks a file and returns the decoded license or an error code. No client state, no network.
  • client.loginFromFile(pathOrText): verifies with the client’s configured app id, public key(s) and HWID, then marks the client authenticated so isAuthenticated(), getLicenseVariables() and getAppVariables() work exactly as after an online login(). It never starts the grace-period timer or online check-ins. logout() clears it.

Rejection codes

Checks run in this fixed order in every SDK; the first failure wins. loginFromFile reports these through onFailure("offline_login_failed", error) and returns false. Unlike login(), it never exits the process on its own: an air-gapped user deserves a message before the app closes.
Offline verification depends on the machine’s clock for expired. A user can extend a file by winding the clock back, exactly like the grace period. Short expiries and HWID binding limit the blast radius; there is no server to consult.

File format (version 1)

The file is text, safe to email or copy through a data diode:
  • Headers are informational. SDKs read every decision-relevant field from the signed payload, never from the headers.
  • Payload (base64 JSON): v (1), typ (authforge-license), appId, licenseKey, jti (unique file id), kid (the app signing key id), issuedAt, expiresAt (ISO 8601 or null), hwid ({ "mode": "bound", "hwids": [...] } or { "mode": "any" }), and optional snapshots taken at mint time: label, licenseExpiresAt, licenseVariables, appVariables.
  • Signed bytes: the Ed25519 signature covers the UTF-8 bytes of the base64 payload string (body lines joined, all whitespace removed). This is the same contract as /auth/validate responses, which is why every SDK reuses its existing verify routine and why there is no JSON canonicalisation step.
  • Keys: the file is signed with your app’s existing per-app Ed25519 key (KMS-protected in AuthForge) and verified with the same public key you already embed in your app. Rotating the app key means new files carry the new kid; SDKs accept a list of public keys, so keep the old one in the list until every old file has expired.
  • Versioning: SDKs reject any v other than 1 with unsupported_version. Future versions will be additive and announced with an SDK release.
Variable snapshots are frozen at mint time. If you change a license variable later, machines running on an existing file keep the old value until you mint and deliver a new file.

Where this fits

  • Default: online login() + grace period. Cheapest, revocable at the next activation, zero operator work.
  • High-value software with connectivity: online login() + online check-ins for fast revocation.
  • Machines that can never connect: offline license files, short-lived and HWID-bound, re-issued on a schedule. See Offline licensing best practices.
Out of scope, by design: bring-your-own signing keys, client-side minting, floating seats, and any claim of remote revocation for files already distributed.