Why returning {"valid": true} for desktop license checks is broken (and how signed leases fix it)
If you're shipping a paid desktop app (Electron, Tauri, .NET, Python, etc), please avoid doing basic HTTP checks like this: const res = await fetch("https://api.myapp.com/verify?key=" + key); const data = await res.js
If you're shipping a paid desktop app (Electron, Tauri, .NET, Python, etc), please avoid doing basic HTTP checks like this:
const res = await fetch("https://api.myapp.com/verify?key=" + key);
const data = await res.json();
if (data.valid) unlockApp();
I see this all the time, and it takes about 30 seconds to bypass. Anyone can just:
- Map your licensing domain to
127.0.0.1in their local hosts file - Run a 5-line local server that returns
{"valid": true}to any request - Unlock the app permanently
On top of that, if a paying user gets on a flight or has spotty WiFi, the request fails and your app locks them out.
The real fix isn't heavy DRM or code obfuscation. It's just asymmetric cryptography.
Instead of sending back plain JSON, your server signs the activation payload (machine identifier, license key, expiration date/lease, and a random nonce) using an Ed25519 private key.
Your desktop app only ships with the Public Key. When the client receives the payload, it verifies the signature locally.
Why this works:
- Even if a user redirects your licensing URL to localhost, their fake server doesn't have your private key. It physically cannot forge an Ed25519 signature that passes the embedded public key check.
- The random nonce prevents replay attacks (recording a real server response and replaying it later).
- Offline support: once verified, you save the signed payload locally. The app can verify the signature and lease expiration date on every launch completely offline. No startup network latency.
I put together a minimal open-source Electron boilerplate demonstrating the client-side signature verification and offline lease storage:
https://github.com/velopre/electron-license-key-example
(For context: I got annoyed by this while building desktop software and ended up turning the backend into Keyfern, mainly because legacy licensing platforms start at $199–€299/mo and meter API requests, which makes zero sense when users launch apps offline. Keyfern is free up to 50 keys and $10/mo for 1,000 keys with no API request limits. But regardless of whether you use a service or write 20 lines of libsodium yourself, please stop relying on unsigned JSON responses on client machines.)
Curious how others here handle machine-locking and clock rollback detection without getting in the way of legitimate users.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.