.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.
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
- Open the license (Applications -> app -> Licenses -> license page) and click Mint .authforge file.
- 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.
-
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 silenthwid_mismatch. You can still paste the getter (getHwid()/get_hwid()/HWID()/hwid()/GetHwid()). Any machine requires an explicit acknowledgement. -
Click Mint & download. The browser downloads
<licenseKey>.authforgeand 1 credit is charged. The file body is never stored server-side, only its SHA-256, so download it now.
Developer API
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
activeand not expired; the app must not be paused. expiresAtmust 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"needsallowUnbound: 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 withno_creditsbefore 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 soisAuthenticated(),getLicenseVariables()andgetAppVariables()work exactly as after an onlinelogin(). 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 ornull),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/validateresponses, 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
vother than 1 withunsupported_version. Future versions will be additive and announced with an SDK release.
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.