Skip to content

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