A Blazor component library for license activation with advanced browser fingerprinting and duplicate detection.
$ dotnet add package Nesco.LicensingBlazor component library for license activation with browser fingerprinting, EULA support, and token-based activation.
dotnet add package Nesco.Licensing
using LicenseActivation.Components.Extensions;
using MudBlazor.Services; // Only if using MudLicenseActivationComponent
// Add MudBlazor (only if using MudLicenseActivationComponent)
builder.Services.AddMudServices();
// Add License Activation services
builder.Services.AddNescoLicensing(options =>
{
options.ApiBaseUrl = "https://your-api-url.com";
options.PublicKey = "your-rsa-public-key"; // Optional for signature validation
});
<!-- Add machine fingerprinting script -->
<script src="_content/LicenseActivation.Components/js/lic.min.js"></script>
@page "/license"
@using LicenseActivation.Components
<MudLicenseActivationComponent
ProductCode="YOUR_PRODUCT_CODE"
AcceptedByName="@userName" @* Optional: Pre-fill EULA name *@
AcceptedByEmail="@userEmail" @* Optional: Pre-fill EULA email *@
MachineFingerprint="@machineId" @* Optional: Override machine fingerprint *@
OnActivationSuccess="OnSuccess"
OnActivationError="OnError" />
@code {
private async Task OnSuccess(TokenActivationResponse result)
{
// Handle successful activation
}
private async Task OnError(string error)
{
// Handle error
}
}
@page "/license"
@using LicenseActivation.Components
<HtmlLicenseActivationComponent
ProductCode="YOUR_PRODUCT_CODE"
AcceptedByName="@userName" @* Optional: Pre-fill EULA name *@
AcceptedByEmail="@userEmail" @* Optional: Pre-fill EULA email *@
MachineFingerprint="@machineId" @* Optional: Override machine fingerprint *@
OnActivationSuccess="OnSuccess"
OnActivationError="OnError" />
@page "/heartbeat-test"
@using LicenseActivation.Components
<HeartbeatTestComponent />
This component provides a complete testing interface for heartbeat functionality with:
| Parameter | Type | Description |
|---|---|---|
ProductCode | string | Required. Product code for license validation |
AcceptedByName | string? | Pre-fill name for EULA acceptance |
AcceptedByEmail | string? | Pre-fill email for EULA acceptance |
MachineFingerprint | string? | Override machine fingerprint. When provided, disables manual input and skips auto-generation |
OnActivationSuccess | EventCallback<TokenActivationResponse> | Success callback |
OnActivationError | EventCallback<string> | Error callback |
The library includes a LicenseHelper class for programmatic license activation and heartbeat operations. It can be used with dependency injection or as a standalone instance.
When using AddNescoLicensing(), the LicenseHelper is automatically registered in the DI container:
@inject LicenseHelper LicenseHelper
// Or in a service/controller:
public class MyService
{
private readonly LicenseHelper _licenseHelper;
public MyService(LicenseHelper licenseHelper)
{
_licenseHelper = licenseHelper;
}
}
Create a standalone instance for console apps or scenarios without DI:
// Create standalone helper
var licenseHelper = LicenseHelper.CreateStandalone(
apiBaseUrl: "https://your-api-url.com",
publicKey: "your-rsa-public-key" // Optional
);
Activates a license with full validation and EULA support:
using Nesco.Licensing.Helpers;
using Nesco.Licensing.Core.Models;
// Basic activation (auto-generates machine fingerprint)
var result = await licenseHelper.ActivateLicenseAsync(
licenseToken: "your-license-token",
productCode: "YOUR_PRODUCT_CODE"
);
if (result.Success)
{
Console.WriteLine($"License activated! Activation ID: {result.ActivationId}");
}
else if (result.RequiresEula)
{
// EULA acceptance is required
Console.WriteLine($"EULA required: {result.RequiredEula?.Name}");
// Retry with EULA acceptance
var eulaAcceptance = new EulaAcceptanceInfo
{
EulaId = result.RequiredEula.Id,
AcceptedByName = "John Doe",
AcceptedByEmail = "john@example.com",
IsAccepted = true
};
result = await licenseHelper.ActivateLicenseAsync(
licenseToken: "your-license-token",
productCode: "YOUR_PRODUCT_CODE",
eulaAcceptance: eulaAcceptance
);
}
else
{
Console.WriteLine($"Activation failed: {result.Error}");
}
// With custom machine fingerprint
var result = await licenseHelper.ActivateLicenseAsync(
licenseToken: "your-license-token",
productCode: "YOUR_PRODUCT_CODE",
machineFingerprint: "custom-machine-id"
);
Sends a heartbeat to verify license validity:
using Nesco.Licensing.Helpers;
// Send heartbeat (auto-generates machine fingerprint)
var heartbeatResult = await licenseHelper.SendHeartbeatAsync(
activationId: myActivationId,
customerEmail: "customer@example.com" // Optional
);
if (heartbeatResult.Success)
{
var response = heartbeatResult.Response;
Console.WriteLine($"License valid: {response.IsValid}");
Console.WriteLine($"Product: {response.ProductName}");
Console.WriteLine($"Expires: {response.ExpiryDate}");
}
else
{
Console.WriteLine($"Heartbeat failed: {heartbeatResult.Error}");
Console.WriteLine($"Error code: {heartbeatResult.ErrorCode}");
}
// With custom machine fingerprint
var heartbeatResult = await licenseHelper.SendHeartbeatAsync(
activationId: myActivationId,
customerEmail: "customer@example.com",
machineFingerprint: "custom-machine-id"
);
public class ActivateLicenseResult
{
public bool Success { get; set; }
public string? Error { get; set; }
public Guid? ActivationId { get; set; }
public ClientLicenseTokenData? TokenInfo { get; set; }
public TokenActivationResponse? ActivationResponse { get; set; }
public bool RequiresEula { get; set; }
public EulaInfo? RequiredEula { get; set; }
public string? MachineFingerprint { get; set; }
}
public class HeartbeatResult
{
public bool Success { get; set; }
public string? Error { get; set; }
public string? ErrorCode { get; set; } // CUSTOMER_MISMATCH, MACHINE_MISMATCH, etc.
public string? ErrorDetails { get; set; }
public Guid ActivationId { get; set; }
public string? CustomerEmail { get; set; }
public string? MachineFingerprint { get; set; }
public string? SecurityToken { get; set; }
public HeartbeatResponse? Response { get; set; }
}
public class LicenseManagementService : BackgroundService
{
private readonly LicenseHelper _licenseHelper;
private readonly IConfiguration _configuration;
private Guid? _activationId;
public LicenseManagementService(
LicenseHelper licenseHelper,
IConfiguration configuration)
{
_licenseHelper = licenseHelper;
_configuration = configuration;
}
public async Task<bool> ActivateLicenseAsync(string token)
{
var result = await _licenseHelper.ActivateLicenseAsync(
licenseToken: token,
productCode: _configuration["License:ProductCode"]
);
if (result.Success)
{
_activationId = result.ActivationId;
// Store activation ID for heartbeats
await SaveActivationIdAsync(result.ActivationId);
return true;
}
return false;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
if (_activationId.HasValue)
{
var heartbeat = await _licenseHelper.SendHeartbeatAsync(
activationId: _activationId.Value,
customerEmail: _configuration["License:CustomerEmail"]
);
if (!heartbeat.Success)
{
// Handle license invalidation
await HandleLicenseInvalidationAsync(heartbeat.ErrorCode);
}
}
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
using Nesco.Licensing.Helpers;
using Nesco.Licensing.Core.Models;
class Program
{
static async Task Main(string[] args)
{
// Create standalone helper
var licenseHelper = LicenseHelper.CreateStandalone(
apiBaseUrl: "https://license-api.example.com",
publicKey: "YOUR_RSA_PUBLIC_KEY"
);
// Activate license
Console.WriteLine("Enter your license token:");
var token = Console.ReadLine();
var result = await licenseHelper.ActivateLicenseAsync(
licenseToken: token,
productCode: "MYPRODUCT"
);
if (result.RequiresEula)
{
Console.WriteLine($"EULA: {result.RequiredEula.Name}");
Console.WriteLine(result.RequiredEula.Content);
Console.WriteLine("\nDo you accept? (y/n)");
if (Console.ReadLine()?.ToLower() == "y")
{
var eulaAcceptance = new EulaAcceptanceInfo
{
EulaId = result.RequiredEula.Id,
AcceptedByName = "User Name",
AcceptedByEmail = "user@example.com",
IsAccepted = true
};
result = await licenseHelper.ActivateLicenseAsync(
licenseToken: token,
productCode: "MYPRODUCT",
eulaAcceptance: eulaAcceptance
);
}
}
if (result.Success)
{
Console.WriteLine($"✓ License activated! ID: {result.ActivationId}");
// Periodic heartbeat
while (true)
{
await Task.Delay(TimeSpan.FromMinutes(5));
var heartbeat = await licenseHelper.SendHeartbeatAsync(
activationId: result.ActivationId.Value,
customerEmail: "user@example.com"
);
if (!heartbeat.Success)
{
Console.WriteLine($"License check failed: {heartbeat.Error}");
break;
}
}
}
else
{
Console.WriteLine($"✗ Activation failed: {result.Error}");
}
}
}
The service is automatically registered when you call AddLicenseActivationComponent(). Available methods:
public interface ILicenseActivationService
{
// Check for duplicate activations
Task<DuplicateCheckResponse?> CheckDuplicateActivationAsync(LicenseActivationRequest request);
// Deactivate a license
Task<bool> DeactivateLicenseAsync(LicenseDeactivationRequest request);
// Validate license status
Task<LicenseValidationResponse?> ValidateLicenseAsync(LicenseValidationRequest request);
// Send secure heartbeat to check activation validity with token-based authentication
// Automatically generates machine fingerprint when MachineFingerprint is empty
Task<HeartbeatResponse?> SendHeartbeatAsync(HeartbeatRequest request);
// Activate with token (handles EULA requirements)
Task<TokenActivationResponse> ActivateWithTokenAsync(TokenActivationRequest request);
}
POST /api/license/check-duplicatePOST /api/license/deactivatePOST /api/license/validatePOST /api/license/heartbeatPOST /api/license/activateCreates unique machine identifiers that are consistent across different browsers on the same machine using:
Both activation components support an optional MachineFingerprint parameter that allows you to override the fingerprint behavior:
<!-- Auto-generated fingerprint (default behavior) -->
<MudLicenseActivationComponent ProductCode="MYPRODUCT" />
<!-- Custom fingerprint via parameter -->
<MudLicenseActivationComponent
ProductCode="MYPRODUCT"
MachineFingerprint="custom-machine-identifier" />
<!-- Using variable from code-behind -->
<HtmlLicenseActivationComponent
ProductCode="MYPRODUCT"
MachineFingerprint="@storedMachineId" />
Behavior:
When MachineFingerprint is provided:
When MachineFingerprint is NOT provided:
Use Cases:
The heartbeat system uses JWT-like tokens for enhanced security validation:
// Inject the service
@inject ILicenseActivationService LicenseService
// Create heartbeat request with security token
var heartbeatRequest = new HeartbeatRequest(
activationId: myActivationId,
customerEmail: "user@company.com",
machineFingerprint: "browser-fingerprint-hash"
);
// Send heartbeat (fingerprint auto-generated if empty)
var response = await LicenseService.SendHeartbeatAsync(heartbeatRequest);
if (response?.IsValid == true)
{
// License is still valid
Console.WriteLine($"License valid until: {response.ExpiryDate}");
}
else
{
// License invalid or expired
Console.WriteLine("License validation failed");
}
public class LicenseHeartbeatService : IHostedService, IDisposable
{
private readonly ILicenseActivationService _licenseService;
private Timer? _timer;
private readonly Guid _activationId;
private readonly string _customerEmail;
private readonly string _machineFingerprint;
public LicenseHeartbeatService(ILicenseActivationService licenseService)
{
_licenseService = licenseService;
// Initialize with your application's values
_activationId = GetStoredActivationId();
_customerEmail = GetCurrentUserEmail();
_machineFingerprint = GetBrowserFingerprint();
}
public Task StartAsync(CancellationToken cancellationToken)
{
// Send heartbeat every 5 minutes
_timer = new Timer(SendHeartbeat, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
return Task.CompletedTask;
}
private async void SendHeartbeat(object? state)
{
try
{
var request = new HeartbeatRequest(_activationId, _customerEmail, _machineFingerprint);
// Machine fingerprint is auto-generated if empty for consistent identification
var response = await _licenseService.SendHeartbeatAsync(request);
if (response?.IsValid != true)
{
// Handle license validation failure
await HandleLicenseInvalidation();
}
}
catch (Exception ex)
{
// Log heartbeat failure
Console.WriteLine($"Heartbeat failed: {ex.Message}");
}
}
private async Task HandleLicenseInvalidation()
{
// Implement your license invalidation logic
// E.g., disable features, show reactivation dialog, etc.
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose() => _timer?.Dispose();
}
{activationId}.{customerEmail}.{machineFingerprint}CheckCustomer and CheckFingerprint control validation behaviorpublic class HeartbeatRequest
{
public string Token { get; set; } // JWT-like security token
// Read-only properties for accessing token components:
public string ActivationId { get; } // Parsed from token
public string CustomerEmail { get; } // Parsed from token
public string MachineFingerprint { get; } // Parsed from token
// Constructor options:
public HeartbeatRequest(string token);
public HeartbeatRequest(Guid activationId, string customerEmail, string machineFingerprint);
}
public class HeartbeatResponse
{
public bool IsValid { get; set; } // Overall validity
public bool IsActiveActivation { get; set; } // Activation status
public bool IsValidLicense { get; set; } // License status
public Guid ActivationId { get; set; }
public string ProductName { get; set; }
public DateTime? ExpiryDate { get; set; }
public string Params { get; set; } // Product parameters
}
The heartbeat system provides specific error messages for different validation failures:
CheckCustomer = true)CheckFingerprint = true)The SendHeartbeatAsync method automatically generates machine fingerprints when empty:
// Create request with empty fingerprint - will be auto-generated
var request = new HeartbeatRequest(activationId, customerEmail, "");
var response = await LicenseService.SendHeartbeatAsync(request);
// Create request with specific fingerprint - will use provided value
var request = new HeartbeatRequest(activationId, customerEmail, "specific-fingerprint");
var response = await LicenseService.SendHeartbeatAsync(request);
Auto-Generation Behavior:
MachineFingerprint is empty string: Automatically generates machine fingerprintMachineFingerprint has value: Uses the provided fingerprintCheckCustomer and CheckFingerprint for production licensesThe ActivationUtilities class provides:
ValidateLicenseToken() - Token validation with product code and signature checksValidateEulaAcceptance() - EULA field validationGetFinalEulaValues() - Priority handling for parameter vs form valuesIsEulaRequired() - Checks if response requires EULAMIT License
Built with Blazor and MudBlazor