All posts
Tutorial Offline

Offline Grace Mode: Keeping Your App Working Without a Connection

PC
PermitCore
September 15, 2026 · 6 min read
The Create License form showing the Offline Grace Days field

Your customer's laptop goes into airplane mode, or their office firewall blocks outbound requests, or api.permitcore.dev itself has a bad five minutes. If your app hard-fails the moment it can't reach the license server, you'll get support tickets from people who paid you money and did nothing wrong. That's what offline grace mode is for.

How It Works

Every successful validate() or activate() call, PermitCore signs a small payload — license status, remaining activations, feature flags, expiry — with the tenant's own ECDSA P-256 private key, and hands it back alongside the normal response. The SDK caches that signed token locally. The next time the network is unreachable, the SDK checks the cached token's signature instead of failing outright.

This matters because a signed cache is fundamentally different from a plain {"isValid":true} file sitting on disk. Anyone can hand-edit a JSON file. Forging a token that verifies against PermitCore's public key requires the tenant's private key, which never leaves the server.

Setting the Grace Period

You control this per license, in the same form you used to generate the key:

Offline Grace Days field with default of 21 days

21 days is a reasonable default — long enough to cover a vacation or a slow IT department, short enough that a permanently offline machine still eventually stops working.

Set it to 0 to disable offline mode entirely for a given license — useful if your product has a hard requirement to phone home (metered billing, for example).

What the SDK Does With It

try
{
    var result = await client.ValidateAsync(key);
    CacheGraceToken(result.GraceToken);   // signed, opaque to the app
    return result;
}
catch (HttpRequestException)
{
    var cached = LoadCachedGraceToken();
    if (cached is null || !VerifySignature(cached))
        return ValidateResult.Fail("No valid offline cache. Reconnect to continue.");

    if (DateTime.UtcNow > cached.ValidUntil)
        return ValidateResult.Fail("Offline grace period expired.");

    return ValidateResult.Ok(cached, offline: true);
}

This logic already ships inside all 6 official SDKs — you don't need to hand-roll signature verification yourself unless you're talking to the REST API directly.

What This Doesn't Protect Against

We'd rather tell you the honest limits than let you find out the hard way. A signed offline cache stops someone from editing the cached file to fabricate a valid result. It does not stop someone from winding their system clock backward to keep an already-issued token looking unexpired — the token's expiry check is only as trustworthy as the local clock it's compared against. It's availability protection for legitimate customers hitting real network problems, not a hardened anti-tamper system for a hostile user actively trying to defeat it.

If your threat model includes users deliberately trying to run past their offline window indefinitely, keep the grace period conservative and pair it with server-side signals (like a max devices-per-license cap) rather than relying on the offline check alone.

What's Next

Offline grace applies to both node-locked and floating licenses, but floating adds its own wrinkle — what happens to a checked-out seat when the checkout machine goes offline and never checks back in? That's the subject of the next post.

Previous: Device Activation Next: Node-Locked vs. Floating