License key generation and validation implementation on website
A license key is a string that encodes rights: which product, which plan, expiration date. Validation must work both online (API) and offline (cryptographic signature verification).
Key formats
Simple random key: A3K7M-XQ2WP-N8VLR-5HZTB-J4YCS
Stores only uniqueness. All license data is on server. Online validation is mandatory.
Key with data (Partial Key Verification): Part of key encodes license attributes. Allows partial offline validation.
JWT token: eyJhbGciOiJSUzI1NiJ9... — full token with payload and RSA signature.
JWT license generation
Asymmetric RSA signature allows client application to verify license without contacting server, using only public key:
use Firebase\JWT\JWT;
class LicenseTokenService
{
public function issue(License $license): string
{
$privateKey = file_get_contents(storage_path('keys/license_private.pem'));
return JWT::encode([
'iss' => 'example.com',
'iat' => now()->timestamp,
'exp' => $license->expires_at?->timestamp ?? 9999999999,
'license_id' => $license->id,
'product' => $license->product_code,
'plan' => $license->plan,
'seats' => $license->max_seats,
'features' => $license->features,
], $privateKey, 'RS256');
}
public function verify(string $token): array
{
$publicKey = file_get_contents(storage_path('keys/license_public.pem'));
$payload = JWT::decode($token, new Key($publicKey, 'RS256'));
return (array) $payload;
}
}
Offline validation in application
// C# example for desktop application
public class LicenseChecker
{
private readonly string publicKey = @"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQEAr...
-----END PUBLIC KEY-----";
public LicenseResult Validate(string token)
{
var handler = new JwtSecurityTokenHandler();
var validation = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "example.com",
ValidateIssuerSigningKey = true,
IssuerSigningKey = GetPublicKey(),
ValidateLifetime = true,
};
try
{
var principal = handler.ValidateToken(token, validation, out _);
return new LicenseResult { IsValid = true, Plan = GetClaim(principal, "plan") };
}
catch (SecurityTokenExpiredException)
{
return new LicenseResult { IsValid = false, Error = "License expired" };
}
}
}
Validation API
Route::post('/api/v1/licenses/validate', function (Request $request) {
$key = $request->input('key');
$license = License::where('key', $key)->first();
if (!$license) {
return response()->json(['valid' => false, 'error' => 'Invalid key'], 404);
}
$checks = [
'active' => $license->status === 'active',
'not_expired'=> !$license->expires_at || now()->isBefore($license->expires_at),
'seats_ok' => $license->activations()->where('revoked', false)->count() < $license->max_activations,
];
$valid = !in_array(false, $checks);
return response()->json([
'valid' => $valid,
'product' => $license->product_code,
'plan' => $license->plan,
'expires_at' => $license->expires_at,
'errors' => array_keys(array_filter($checks, fn($v) => !$v)),
]);
});
Timeline
License key generation and validation with JWT and API: 4–6 working days.







