Security Best Practices

How to make your license integration resistant to cracking, reverse engineering, and tampering. OnyxAuth signs every validation response with Ed25519, but what you do client-side matters just as much.

Response Signature Verification

Every response from /api/v1/validate is signed with your project's Ed25519 private key. Your public key is available in the dashboard. Without signature verification, an attacker can trivially fake responses by pointing your API calls to a local server that returns { "valid": true }.

How it works

  1. Your app sends a validation request with an optional nonce (random string)
  2. OnyxAuth validates the license and builds a response payload
  3. The payload is signed with your project's Ed25519 private key (stored server-side, never exposed)
  4. Your app receives the response and verifies the signature using the public key embedded in your binary
  5. If the signature is invalid or the nonce doesn't match, the response has been tampered with

Canonical Signing Payload

The signature covers a JSON string built from these fields in this exact order. To verify, reconstruct this exact JSON and verify against the signature field.

Signing payload structurejson
{
  "licenseId": "550e8400-e29b-41d4-a716-446655440000",
  "valid": true,
  "expiresAt": "2026-12-31T23:59:59.000Z",
  "timeLeft": 31536000,
  "productName": "My App",
  "signedAt": "2026-02-12T10:30:00.000Z",
  "nonce": "your-random-nonce"
}

Nonce (Replay Prevention)

Without a nonce, an attacker could capture a legitimate signed response and replay it forever. By sending a fresh random nonce with each request and verifying it's echoed back in the signed payload, you ensure the response was generated specifically for this request.

Generating and verifying a noncejavascript
// 1. Generate a random nonce before each request
const nonce = crypto.randomBytes(16).toString('hex');

// 2. Include it in the validation request
const response = await fetch('/api/v1/validate', {
  method: 'POST',
  body: JSON.stringify({ clientId, licenseKey, fingerprint, nonce })
});

// 3. After verifying the signature, check the nonce matches
if (data.nonce !== nonce) {
  throw new Error('Nonce mismatch - possible replay attack');
}

Integration Hardening

Signature verification stops response forgery, but a determined attacker can still patch your binary. These techniques make that significantly harder.

1. Scatter License Checks

Don't check the license in one place. Validate at startup, periodically during runtime, and before sensitive operations. If an attacker patches one check, the others still catch it. Use different code paths for each check so they can't NOP a single function.

2. Avoid Boolean Flags

Never store the validation result in a single isLicensed = true variable. A cracker can find this in memory or in the binary with a simple search. Instead, derive the "licensed" state from the cryptographic verification itself. For example, use data from the signed response (like metadata or product name) as input to unlock functionality.

3. Periodic Re-validation (Heartbeat)

Don't only validate at startup. Re-validate periodically (every 15-60 minutes) during runtime. Use the signedAt timestamp in responses to detect stale cached responses. If validation fails mid-session, degrade functionality gracefully rather than crashing (which makes it obvious where the check is).

4. Binary Integrity Verification

Compute a checksum of your critical code sections at build time, then verify them at runtime. If an attacker patches your binary to skip the license check, the checksum will mismatch. Store the expected checksum in an obfuscated form.

5. Anti-Debugging

Detect debuggers and disassemblers at runtime. This raises the bar for reverse engineering.

PlatformTechnique
WindowsIsDebuggerPresent(), CheckRemoteDebuggerPresent(), timing checks
macOS/Linuxptrace(PTRACE_TRACEME), /proc/self/status TracerPid check
.NET / JavaDebugger.IsAttached, timing-based detection

6. Code Obfuscation

Obfuscate the license check code and signature verification to make static analysis harder. Choose tools appropriate for your language:

LanguageRecommended Tools
C / C++VMProtect, Themida, LLVM Obfuscator
C# / .NET.NET Reactor, ConfuserEx, Dotfuscator
JavaProGuard, Zelix KlassMaster, Allatori
Rustobfstr (string obfuscation), release builds with LTO + strip
Gogarble, gobfuscate
PythonPyArmor, Cython compilation, Nuitka

7. Feature Gating via Server Data

Instead of just gating on a boolean, use the signed response data your application needs to function. Store feature flags and configuration in your license's metadata field (set via the management API), then fetch it alongside validation. If the validation is skipped entirely, the application lacks the data it needs to work. This makes "just NOP the check" impossible because the check delivers functional requirements, not just a gate.

Example: using license data for feature gatingjavascript
// On your license, set metadata like:
// { "features": ["export", "analytics"], "encryptionSeed": "x9k2m..." }

const result = await validateLicense(clientId, key, fingerprint);

// Use the signed productName or other response fields
// to derive which features to enable
if (!result.valid) {
  disableAllFeatures();
  return;
}

// Use the productName from the signed response to determine tier
const tier = result.productName; // e.g. "My App Pro"
if (tier.includes('Pro')) {
  enableExportModule();
  enableAnalyticsDashboard();
}

Security Checklist

1
Verify Ed25519 signature on every validation response
2
Send a random nonce with each request and verify it's echoed back
3
Embed the public key in your binary (not fetched at runtime)
4
Scatter license checks across multiple code paths
5
Re-validate periodically, not just at startup
6
Don't store results in a single boolean flag
7
Use license metadata to deliver functional data
8
Obfuscate the license check and signature verification code
9
Implement binary integrity checks (self-checksumming)
10
Add anti-debugging detection

No protection is unbreakable. The goal is to raise the cost of cracking high enough that it's not worth the effort. Signature verification alone stops the vast majority of casual cracking attempts. Combined with obfuscation and the techniques above, you're well protected.

See the License Validation docs for full code examples in all 9 supported languages.