ASP.NET Core  

Implementing a Custom License Key Generator in ASP.NET Core

Modern SaaS and desktop software often requires secure license key generation. While many teams rely on third-party licensing providers, building your own system in ASP.NET Core gives you full control over:

  • Key format

  • Activation limits

  • Expiration & renewal

  • Machine binding

  • Offline validation

  • Logging & tracking

This article shows how to create a professional-grade license key generator and validator using ASP.NET Core 8, including database schema, services, and a secure signing mechanism.

1. Requirements and Design Goals

Your custom licensing system should support:

  1. Key generation

    • Unique, unpredictable license keys

    • Expiration date

    • Optional device/machine binding

    • Usage count or activation limit

    • Optional edition-level restrictions (Basic, Pro, Enterprise)

  2. Key validation API

    • Checks signature

    • Verifies expiration

    • Verifies activation count

    • Registers activations

  3. Security

    • Use asymmetric cryptography (RSA 2048+)

    • License keys must be tamper-proof

    • Server holds private key; client uses public key

  4. Ease of export

    • Create JSON + printable license files

    • Support offline verification

2. Database Structure (SQL Server)

License Table

CREATE TABLE Licenses (
    LicenseId UNIQUEIDENTIFIER PRIMARY KEY,
    CustomerName NVARCHAR(200),
    Email NVARCHAR(200),
    LicenseKey NVARCHAR(500),
    Edition NVARCHAR(50),
    ExpirationDate DATETIME,
    MaxActivations INT,
    CreatedOn DATETIME DEFAULT GETDATE()
);

Activation Table

CREATE TABLE LicenseActivations (
    ActivationId UNIQUEIDENTIFIER PRIMARY KEY,
    LicenseId UNIQUEIDENTIFIER,
    MachineId NVARCHAR(200),
    ActivatedOn DATETIME DEFAULT GETDATE()
);

3. License Payload Structure

Before generating a license key, create a payload object that describes the license.

public class LicensePayload
{
    public Guid LicenseId { get; set; }
    public string Edition { get; set; }
    public DateTime ExpirationDate { get; set; }
    public int MaxActivations { get; set; }
    public string CustomerName { get; set; }
    public string Email { get; set; }
}

This payload will be serialized to JSON → signed with RSA → encoded as Base64 → distributed as Key.

4. RSA Key Pair (One-time setup)

Generate RSA keys once:

openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private.pem -out public.pem

Store private.pem securely (do NOT commit to Git).
Clients will use public.pem to validate offline.

5. License Signing Service (ASP.NET Core)

Load RSA keys

public class RsaKeyService
{
    private readonly RSA _privateKey;
    private readonly RSA _publicKey;

    public RsaKeyService(IWebHostEnvironment env)
    {
        _privateKey = RSA.Create();
        _privateKey.ImportFromPem(File.ReadAllText(Path.Combine(env.ContentRootPath, "Keys/private.pem")));

        _publicKey = RSA.Create();
        _publicKey.ImportFromPem(File.ReadAllText(Path.Combine(env.ContentRootPath, "Keys/public.pem")));
    }

    public RSA PrivateKey => _privateKey;
    public RSA PublicKey => _publicKey;
}

6. Generating a License Key

public class LicenseGenerator
{
    private readonly RsaKeyService _rsa;

    public LicenseGenerator(RsaKeyService rsa)
    {
        _rsa = rsa;
    }

    public string GenerateLicense(LicensePayload payload)
    {
        string json = JsonSerializer.Serialize(payload);

        // sign the payload
        byte[] data = Encoding.UTF8.GetBytes(json);
        byte[] signature = _rsa.PrivateKey.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

        // combine payload + signature
        var licenseFile = new
        {
            Payload = payload,
            Signature = Convert.ToBase64String(signature)
        };

        string finalJson = JsonSerializer.Serialize(licenseFile);
        return Convert.ToBase64String(Encoding.UTF8.GetBytes(finalJson));
    }
}

This results in a compact Base64 license key.

7. License Validation API (Server-side)

Clients call this API to validate their key.

[HttpPost("validate")]
public async Task<IActionResult> Validate([FromBody] string licenseKey)
{
    var json = Encoding.UTF8.GetString(Convert.FromBase64String(licenseKey));
    var obj = JsonSerializer.Deserialize<LicenseFile>(json);

    byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(obj.Payload));
    byte[] signatureBytes = Convert.FromBase64String(obj.Signature);

    bool valid = _rsa.PublicKey.VerifyData(
        payloadBytes,
        signatureBytes,
        HashAlgorithmName.SHA256,
        RSASignaturePadding.Pkcs1
    );

    if (!valid)
        return Unauthorized("Invalid license key");

    // Check expiration
    if (obj.Payload.ExpirationDate < DateTime.UtcNow)
        return Unauthorized("License expired");

    // Check activation limit
    int count = await _db.LicenseActivations
        .CountAsync(a => a.LicenseId == obj.Payload.LicenseId);

    if (count >= obj.Payload.MaxActivations)
        return Unauthorized("Activation limit reached");

    return Ok("Valid");
}

8. Activation Registration (Server-side)

When the client first activates:

[HttpPost("activate")]
public async Task<IActionResult> Activate(ActivationRequest req)
{
    // validate license key first
    // ... (same as previous section)

    _db.LicenseActivations.Add(new LicenseActivation
    {
        ActivationId = Guid.NewGuid(),
        LicenseId = obj.Payload.LicenseId,
        MachineId = req.MachineId
    });

    await _db.SaveChangesAsync();

    return Ok("Activation successful");
}

9. Client-side Offline Validation (C#)

Client apps should validate the license locally even without internet:

public bool ValidateOffline(string licenseKey, string publicKeyPem)
{
    var rsa = RSA.Create();
    rsa.ImportFromPem(publicKeyPem);

    var json = Encoding.UTF8.GetString(Convert.FromBase64String(licenseKey));
    var obj = JsonSerializer.Deserialize<LicenseFile>(json);

    byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(obj.Payload));
    byte[] signatureBytes = Convert.FromBase64String(obj.Signature);

    bool valid = rsa.VerifyData(
        payloadBytes,
        signatureBytes,
        HashAlgorithmName.SHA256,
        RSASignaturePadding.Pkcs1
    );

    return valid && obj.Payload.ExpirationDate > DateTime.UtcNow;
}

10. Generating a Printable License File (JSON)

var licenseJson = Encoding.UTF8.GetString(Convert.FromBase64String(licenseKey));
File.WriteAllText("license.json", licenseJson);

Clients can import this file for offline verification.

11. Enhancements for Production

Add the following for enterprise-level robustness:

  • License revocation list

  • Encrypted machine IDs (SHA256)

  • License renewal workflow

  • Webhook for usage reporting

  • Admin dashboard to manage licenses

  • Export audit logs

  • Multi-tenant licensing for SaaS

  • Hardware-bound fingerprinting

12. Multi-Environment Licensing (Dev, QA, UAT, Prod)

In real-world enterprise applications, licensing usually differs across environments.
To support this, your license payload and validation services can include an Environment attribute that restricts where a license can be used.

Why it’s needed

  • Prevent developers from using production-grade licenses in dev machines

  • Allow QA teams to simulate expiration, upgrades, revocations

  • Provide controlled testing environments for partners

Add to LicensePayload

public string Environment { get; set; }  // Dev, QA, UAT, Prod

Validation rule

if (obj.Payload.Environment != currentEnvironment)
    return Unauthorized("License not valid for this environment");

This enables strict environment-based validation and prevents misuse of production licenses.

13. Rate-Limited License Validation API (Anti-Abuse Layer)

When products validate frequently (e.g., every startup), it may create opportunities for:

  • Abuse

  • DDOS attempts

  • Excess API load

  • Bots verifying random keys

Adding rate limiting ensures controlled usage.

ASP.NET Core Built-in Rate Limiting

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("licenseValidation", limiterOptions =>
    {
        limiterOptions.PermitLimit = 20;
        limiterOptions.Window = TimeSpan.FromMinutes(1);
        limiterOptions.QueueLimit = 5;
        limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
    });
});

Apply to validation endpoint

[EnableRateLimiting("licenseValidation")]
[HttpPost("validate")]
public IActionResult ValidateLicense([FromBody] string licenseKey)
{
    // validation logic
}

14. Conclusion

This guide demonstrates a full, secure, scalable license key system using ASP.NET Core. It includes key generation, RSA signing, secure storage, activation tracking, and online/offline validation.