License Verification
Detailed guide on implementing license verification in your applications, including caching strategies, offline support, and best practices.
Basic License Verification
Simple Verification
The most basic license verification checks if a key is valid:
bash
curl -X POST https://your-license-api.com/v1/verify-license \
-H "Content-Type: application/json" \
-d '{
"key": "7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28"
}'Response:
json
{
"valid": true,
"tier": "pro",
"product_id": "your-product",
"issued_to": "customer@company.com",
"expires_at": "2025-12-31T23:59:59Z",
"limits": {
"users": 100,
"api_calls": 10000
}
}Verification with Instance Tracking
For applications that need activation tracking:
bash
curl -X POST https://your-license-api.com/v1/validate-license \
-H "Content-Type: application/json" \
-d '{
"key": "7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28",
"instance_id": "server-001"
}'Implementation Patterns
1. Startup Verification
Verify license when your application starts:
javascript
class ApplicationBootstrap {
constructor(licenseKey, apiUrl) {
this.licenseKey = licenseKey;
this.apiUrl = apiUrl;
this.licenseInfo = null;
}
async initialize() {
try {
// Verify license before starting application
const verification = await this.verifyLicense();
if (!verification.valid) {
throw new Error(`Invalid license: ${verification.error}`);
}
this.licenseInfo = verification;
console.log(`Application licensed to: ${verification.issued_to}`);
console.log(`License tier: ${verification.tier}`);
// Start application with license constraints
this.startApplication(verification);
} catch (error) {
console.error('License verification failed:', error.message);
this.showLicenseError(error);
}
}
async verifyLicense() {
const response = await fetch(`${this.apiUrl}/verify-license`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: this.licenseKey })
});
return response.json();
}
startApplication(licenseInfo) {
// Apply license limits to application features
if (licenseInfo.limits.users) {
this.setMaxUsers(licenseInfo.limits.users);
}
if (licenseInfo.limits.api_calls) {
this.setAPICallLimit(licenseInfo.limits.api_calls);
}
console.log('Application started successfully');
}
}
// Usage
const app = new ApplicationBootstrap(
process.env.LICENSE_KEY,
'https://your-license-api.com/v1'
);
app.initialize();2. Periodic Verification
Continuously verify license validity during runtime:
javascript
class PeriodicLicenseChecker {
constructor(licenseKey, apiUrl, intervalMinutes = 60) {
this.licenseKey = licenseKey;
this.apiUrl = apiUrl;
this.interval = intervalMinutes * 60 * 1000;
this.isRunning = false;
this.intervalId = null;
this.lastVerification = null;
}
start() {
if (this.isRunning) return;
this.isRunning = true;
console.log(`Starting periodic license verification every ${this.interval / 60000} minutes`);
// Initial check
this.checkLicense();
// Schedule periodic checks
this.intervalId = setInterval(() => {
this.checkLicense();
}, this.interval);
}
stop() {
if (!this.isRunning) return;
this.isRunning = false;
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
console.log('Stopped periodic license verification');
}
async checkLicense() {
try {
const verification = await this.verifyLicense();
this.lastVerification = {
timestamp: new Date(),
result: verification
};
if (!verification.valid) {
console.error('License verification failed:', verification.error);
this.handleInvalidLicense(verification);
} else {
console.log('License verification successful');
this.handleValidLicense(verification);
}
} catch (error) {
console.error('License verification error:', error.message);
this.handleVerificationError(error);
}
}
async verifyLicense() {
const response = await fetch(`${this.apiUrl}/verify-license`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: this.licenseKey })
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
handleValidLicense(verification) {
// Check if license is expiring soon
if (verification.expires_at) {
const expiryDate = new Date(verification.expires_at);
const daysUntilExpiry = (expiryDate - new Date()) / (1000 * 60 * 60 * 24);
if (daysUntilExpiry <= 30) {
console.warn(`License expires in ${Math.floor(daysUntilExpiry)} days`);
this.notifyExpiryWarning(daysUntilExpiry);
}
}
}
handleInvalidLicense(verification) {
// License is invalid - decide on action
if (verification.expired) {
this.handleExpiredLicense();
} else if (verification.revoked) {
this.handleRevokedLicense();
} else {
this.handleGenericInvalidLicense();
}
}
handleVerificationError(error) {
// Network or server errors - don't immediately shut down
const gracePeriodHours = 4;
const lastSuccess = this.getLastSuccessfulVerification();
if (lastSuccess && (Date.now() - lastSuccess) < gracePeriodHours * 60 * 60 * 1000) {
console.warn(`License verification failed, but within grace period. Continuing operation.`);
} else {
console.error('Grace period exceeded. Application should shut down.');
this.initiateGracefulShutdown();
}
}
handleExpiredLicense() {
console.error('License has expired');
this.showExpiryDialog();
// Optionally shut down application
}
handleRevokedLicense() {
console.error('License has been revoked');
this.initiateGracefulShutdown();
}
handleGenericInvalidLicense() {
console.error('License is invalid');
this.showLicenseErrorDialog();
}
getLastSuccessfulVerification() {
return this.lastVerification?.result?.valid ?
this.lastVerification.timestamp.getTime() : null;
}
notifyExpiryWarning(daysRemaining) {
// Show user notification about upcoming expiry
console.warn(`License expires in ${Math.floor(daysRemaining)} days`);
}
showExpiryDialog() {
// Show expiry dialog to user
console.log('Please renew your license');
}
showLicenseErrorDialog() {
// Show license error dialog
console.log('License error - please contact support');
}
initiateGracefulShutdown() {
console.log('Initiating graceful shutdown due to license issues');
// Implement graceful shutdown logic
process.exit(1);
}
}
// Usage
const licenseChecker = new PeriodicLicenseChecker(
process.env.LICENSE_KEY,
'https://your-license-api.com/v1',
60 // Check every hour
);
licenseChecker.start();
// Graceful shutdown
process.on('SIGTERM', () => {
licenseChecker.stop();
});Caching Strategies
1. In-Memory Caching
Cache verification results to reduce API calls:
javascript
class CachedLicenseVerifier {
constructor(apiUrl, cacheMinutes = 30) {
this.apiUrl = apiUrl;
this.cache = new Map();
this.cacheTTL = cacheMinutes * 60 * 1000;
}
async verifyLicense(key) {
// Check cache first
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
console.log('Returning cached license verification');
return { ...cached.result, fromCache: true };
}
try {
// Verify with API
const result = await this.verifyWithAPI(key);
// Cache successful results
if (result.valid) {
this.cache.set(key, {
result,
timestamp: Date.now()
});
}
return result;
} catch (error) {
// If API fails, return cached result if available
if (cached) {
console.warn('API verification failed, using cached result');
return { ...cached.result, fromCache: true, warning: 'Offline verification' };
}
throw error;
}
}
async verifyWithAPI(key) {
const response = await fetch(`${this.apiUrl}/verify-license`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key })
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
clearCache(key = null) {
if (key) {
this.cache.delete(key);
} else {
this.cache.clear();
}
}
getCacheStats() {
const now = Date.now();
let valid = 0, expired = 0;
for (const [key, cached] of this.cache.entries()) {
if (now - cached.timestamp < this.cacheTTL) {
valid++;
} else {
expired++;
}
}
return { total: this.cache.size, valid, expired };
}
}2. Persistent Caching
Store verification results on disk for offline support:
javascript
const fs = require('fs').promises;
const path = require('path');
class PersistentLicenseCache {
constructor(cacheDir = './license-cache') {
this.cacheDir = cacheDir;
this.ensureCacheDir();
}
async ensureCacheDir() {
try {
await fs.mkdir(this.cacheDir, { recursive: true });
} catch (error) {
console.error('Failed to create cache directory:', error);
}
}
getCacheFilePath(key) {
const hasher = require('crypto').createHash('sha256');
hasher.update(key);
const keyHash = hasher.digest('hex').substring(0, 16);
return path.join(this.cacheDir, `license-${keyHash}.json`);
}
async getCachedVerification(key, maxAgeMinutes = 60) {
try {
const filePath = this.getCacheFilePath(key);
const data = await fs.readFile(filePath, 'utf8');
const cached = JSON.parse(data);
const age = Date.now() - cached.timestamp;
const maxAge = maxAgeMinutes * 60 * 1000;
if (age < maxAge) {
return { ...cached.result, fromCache: true };
} else {
// Cache expired
await this.removeCachedVerification(key);
return null;
}
} catch (error) {
// Cache file doesn't exist or is corrupted
return null;
}
}
async setCachedVerification(key, result) {
try {
const filePath = this.getCacheFilePath(key);
const cached = {
result,
timestamp: Date.now(),
key: key.substring(0, 8) + '...' // Partial key for identification
};
await fs.writeFile(filePath, JSON.stringify(cached, null, 2));
} catch (error) {
console.error('Failed to cache verification:', error);
}
}
async removeCachedVerification(key) {
try {
const filePath = this.getCacheFilePath(key);
await fs.unlink(filePath);
} catch (error) {
// File might not exist, ignore error
}
}
async clearAllCache() {
try {
const files = await fs.readdir(this.cacheDir);
const licenseFiles = files.filter(f => f.startsWith('license-'));
await Promise.all(
licenseFiles.map(file =>
fs.unlink(path.join(this.cacheDir, file))
)
);
console.log(`Cleared ${licenseFiles.length} cached license files`);
} catch (error) {
console.error('Failed to clear cache:', error);
}
}
async getCacheInfo() {
try {
const files = await fs.readdir(this.cacheDir);
const licenseFiles = files.filter(f => f.startsWith('license-'));
const cacheInfo = [];
for (const file of licenseFiles) {
try {
const filePath = path.join(this.cacheDir, file);
const data = await fs.readFile(filePath, 'utf8');
const cached = JSON.parse(data);
cacheInfo.push({
file,
key: cached.key,
timestamp: new Date(cached.timestamp),
age: Date.now() - cached.timestamp,
valid: cached.result.valid
});
} catch (error) {
console.warn(`Corrupted cache file: ${file}`);
}
}
return cacheInfo;
} catch (error) {
console.error('Failed to get cache info:', error);
return [];
}
}
}
// Combined verifier with persistent cache
class RobustLicenseVerifier {
constructor(apiUrl) {
this.apiUrl = apiUrl;
this.cache = new PersistentLicenseCache();
}
async verifyLicense(key, options = {}) {
const {
useCache = true,
cacheMinutes = 60,
offlineMode = false
} = options;
// Try cache first if enabled
if (useCache) {
const cached = await this.cache.getCachedVerification(key, cacheMinutes);
if (cached) {
console.log('Using cached license verification');
return cached;
}
}
// If offline mode, return failure if no cache
if (offlineMode) {
return {
valid: false,
error: 'Offline mode and no cached verification available'
};
}
try {
// Verify online
const result = await this.verifyOnline(key);
// Cache successful verifications
if (result.valid && useCache) {
await this.cache.setCachedVerification(key, result);
}
return result;
} catch (error) {
// API failed, try to use cached result
if (useCache) {
const cached = await this.cache.getCachedVerification(key, cacheMinutes * 10); // Extended grace period
if (cached) {
console.warn('API verification failed, using cached result');
return {
...cached,
warning: 'Verification failed, using cached result'
};
}
}
throw error;
}
}
async verifyOnline(key) {
const response = await fetch(`${this.apiUrl}/verify-license`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key })
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
}Feature Gating Based on License
License-Based Feature Control
javascript
class FeatureManager {
constructor(licenseInfo) {
this.licenseInfo = licenseInfo;
this.features = new Map();
this.initializeFeatures();
}
initializeFeatures() {
const tier = this.licenseInfo.tier;
const limits = this.licenseInfo.limits || {};
// Define features based on license tier
this.features.set('basic_features', true);
this.features.set('advanced_features', ['pro', 'enterprise'].includes(tier));
this.features.set('premium_features', tier === 'enterprise');
this.features.set('api_access', true);
this.features.set('bulk_operations', tier !== 'basic');
this.features.set('custom_integrations', tier === 'enterprise');
// Set usage limits
this.setUsageLimits(limits);
}
setUsageLimits(limits) {
this.limits = {
maxUsers: limits.users || Infinity,
maxAPICallsPerMonth: limits.api_calls || Infinity,
maxStorageGB: limits.storage_gb || Infinity,
maxProjects: limits.projects || Infinity
};
}
isFeatureEnabled(featureName) {
return this.features.get(featureName) || false;
}
checkUsageLimit(limitType, currentUsage) {
const limit = this.limits[limitType];
if (limit === Infinity) return { allowed: true };
const allowed = currentUsage < limit;
const remaining = Math.max(0, limit - currentUsage);
const percentage = (currentUsage / limit) * 100;
return {
allowed,
limit,
current: currentUsage,
remaining,
percentage
};
}
getFeatureList() {
return Array.from(this.features.entries()).map(([name, enabled]) => ({
name,
enabled
}));
}
getLimits() {
return { ...this.limits };
}
}
// Usage in application
class MyApplication {
constructor(licenseKey) {
this.licenseKey = licenseKey;
this.featureManager = null;
}
async initialize() {
// Verify license
const verifier = new RobustLicenseVerifier('https://your-license-api.com/v1');
const licenseInfo = await verifier.verifyLicense(this.licenseKey);
if (!licenseInfo.valid) {
throw new Error('Invalid license');
}
// Initialize feature management
this.featureManager = new FeatureManager(licenseInfo);
console.log('Available features:', this.featureManager.getFeatureList());
console.log('Usage limits:', this.featureManager.getLimits());
}
async createProject(projectData) {
// Check if feature is enabled
if (!this.featureManager.isFeatureEnabled('basic_features')) {
throw new Error('Project creation not available in your license');
}
// Check usage limits
const currentProjects = await this.getCurrentProjectCount();
const limitCheck = this.featureManager.checkUsageLimit('maxProjects', currentProjects);
if (!limitCheck.allowed) {
throw new Error(`Project limit reached: ${limitCheck.current}/${limitCheck.limit}`);
}
// Create project
return this.doCreateProject(projectData);
}
async makeAPICall() {
const currentCalls = await this.getCurrentAPICallCount();
const limitCheck = this.featureManager.checkUsageLimit('maxAPICallsPerMonth', currentCalls);
if (!limitCheck.allowed) {
throw new Error(`API call limit reached: ${limitCheck.current}/${limitCheck.limit}`);
}
if (limitCheck.percentage > 80) {
console.warn(`API usage warning: ${limitCheck.percentage.toFixed(1)}% of limit used`);
}
return this.doMakeAPICall();
}
// Placeholder methods
async getCurrentProjectCount() { return 0; }
async getCurrentAPICallCount() { return 0; }
async doCreateProject(data) { return { id: 1, ...data }; }
async doMakeAPICall() { return { success: true }; }
}Best Practices
1. Network Resilience
- Implement retry logic with exponential backoff
- Use cached results when API is unavailable
- Set appropriate timeouts for verification requests
2. Security
- Never log full license keys
- Use secure HTTP (TLS) for all requests
- Validate license on server-side for web applications
3. Performance
- Cache verification results appropriately
- Use bulk verification for multiple licenses
- Monitor verification response times
4. User Experience
- Show meaningful error messages
- Provide grace periods for temporary network issues
- Display license status and expiry information
5. Monitoring
- Track verification success/failure rates
- Monitor cache hit rates
- Set up alerts for license-related issues
Next Steps
- License Activation - Track instance activations
- Usage Tracking - Monitor license usage
- Client Examples - Implementation examples in various languages