
Features
Every capability of the licensing platform, from token cryptography to self-service registration.
RSA-256 Signed Tokens
Every license key is a JWT signed with RS256. Keys can be managed locally via PEM file or remotely through Azure Key Vault where the private key never leaves the vault.
JWE Encryption
When a consuming application provides its RSA public key during activation, the server returns a JWE token (RSA-OAEP + A256CBC-HS512). Only the consumer can decrypt and read the claims.
License Issuance
Generate license keys against a product and pricing tier via a cascading form with product/tier selection, billing period (None through 36mo or custom date), and feature checklist. Each token carries tier, features, limits, and heartbeat policy.
Per-Machine Activation
Track activations by machine identifier and IP address. Store the consumer encryption public key (PEM-encoded) for JWE. Activating a key auto-revokes sibling keys for the same customer and product.
Seat Enforcement
MaxUsers and MaxTenants are enforced at activation time. If the seat limit for a tier has been reached, activation is rejected. Deactivating a machine frees a seat for reuse.
Heartbeat Renewal
Consuming applications call /heartbeat with their ClientId. The server returns a fresh short-lived token (configurable TTL, default 48h). No heartbeat means the token expires and the application stops working.
License Revocation
Revoke any license with a reason and timestamp. Revocation is checked during validation and heartbeat. Revoking a license instantly invalidates access for the consuming application.
Token Audit
The audit endpoint compares stored JWT claims against the expected state from the current database. Detects drift between what the token says and what it should say, including for JWE-encrypted tokens.
Token Regeneration
Regenerate a license token on demand without changing the license record. Useful when tier features change, pricing tiers are updated, or token claims need to be refreshed after database changes.
Hierarchical Pricing Tiers
Tiers can inherit all features from a parent tier via IncludesTierId. Circular reference protection with a visited set guard. Each tier configures monthly/annual price, user/tenant limits, billing periods, and heartbeat requirement.
Feature Flags in Tokens
Named feature keys (e.g., "sso", "passkeys", "multi_tenant") are embedded in JWT claims. Consuming applications read these at runtime to gate functionality without network calls.
Display-Only Features
Mark features as display-only so they appear in marketing and pricing pages but are excluded from the actual JWT token claims. Clean separation of marketing and enforcement.
Drag-and-Drop Feature Management
Admin panel features a sortable feature list powered by @dnd-kit. Reorder product features with drag-and-drop and persist sort order to the database for consistent display across catalog and dashboard.
Pending License Claims
Email-based license invites for users who do not yet have an account. Admin can resend invites. Claims are fulfilled automatically when the user registers and logs in.
System Logs & Monitoring
Event log viewer in the admin panel with DataGrid pagination and search. Real-time log level adjustment via LogLevelSelector component. API health check endpoint for monitoring.
License Lifecycle
> 1. Issue License
POST /license-keys
Product: Rdn.Identity
Tier: Enterprise
Customer: customer@example.com
Billing: 12 months
Features: sso, passkeys, mfa, webhooks
Result: RSA-256 signed JWT + ClientId
> 2. Validate Token
POST /license-keys/validate
Input: Raw keyToken
Checks: Signature, expiry, revocation
Returns: licenseId, decoded claims, features
> 3. Activate
POST /license-activations/activate
ClientId: guid
MachineId: HWID-ABC-123
PublicKey: RSA-2048 PEM (optional)
Returns: JWE token + clientId + audience
> 4. Heartbeat Loop
POST /license-keys/heartbeat
ClientId: guid
Interval: Configurable (e.g., 60 min)
Token TTL: 48 hours (default)
No heartbeat: Token expires, app goes dark
> 5. Audit
GET /license-keys/:id/audit
Compares: Token claims vs. DB state
Detects: Drift, missing features, stale limits
JWE: Consistent but encrypted
Action: Regenerate token to fix drift
> 6. Revoke
POST /license-keys/:id/revoke
Reason: "Subscription cancelled"
Recorded: RevokedAt, RevokedReason
Effect: Next heartbeat returns invalid
Domain Model
8 core entities with audit tracking (IAuditable), soft deletes (ISoftDeletable), and EF Core configurations.
Product
Name, Description, Audience, LogoUrl, IsActive, SortOrder
ProductFeature
FeatureKey, IsActive, IsDisplayOnly, SortOrder
PricingTier
MonthlyPrice, AnnualPrice, MaxUsers, MaxTenants, IncludesTierId
PricingTierFeature
PricingTierId, ProductFeatureId (junction)
Customer
IdentityUserId, Email, Company, IsActive
LicenseKey
KeyToken [Protected], ClientId, ExpiresAt, FeatureKeys[], HardwareId
LicenseActivation
MachineIdentifier, IpAddress, EncryptionPublicKey
PendingLicenseClaim
Email, FirstName, LastName, Status (Pending/Fulfilled/Expired)
> Product Graph
Product 1:N ProductFeature
Product 1:N PricingTier
Product 1:N LicenseKey
PricingTier N:M ProductFeature (via PricingTierFeature)
PricingTier 0:1 PricingTier (IncludesTierId self-ref)
> License Graph
Customer 1:N LicenseKey
LicenseKey 1:N LicenseActivation
LicenseKey N:1 PricingTier
PendingLicenseClaim N:1 Product
PendingLicenseClaim N:1 PricingTier
Permission-Based Access Control
38 fine-grained permissions across 8 resource categories, enforced via [HasPermission] attribute on every endpoint.
Products
5List, Read, Create, Update, Delete
Product Features
5List, Read, Create, Update, Delete
Pricing Tiers
5List, Read, Create, Update, Delete
Customers
5List, Read, Create, Update, Delete
License Keys
9List, Read, Create, Delete, Revoke, Validate, Regenerate, Audit, Heartbeat
Activations
3List, Activate, Deactivate
Pending Claims
4List, Read, Delete, ResendInvite
System Logs
2List, Update
Self-Service Registration
Register
Customer visits /register with product and tier in query params. A PendingLicenseClaim is created with Pending status.
Identity Invite
The License API calls Rdn.Identity to create the user account and send an email invite with setup instructions.
Set Password
Customer receives the email, follows the link to Rdn.Identity, and sets their password to activate their account.
Claim License
Customer logs in and calls /user/licenses (claim). The pending claim status changes to Fulfilled and a license key is issued.
Activation Flow
> Consumer App → License Server
1. User enters Token + API Key (ClientId) on /activate page
2. Consumer calls POST /license-keys/validate with keyToken
3. Consumer calls POST /license-activations/activate via X-Api-Key header
├─ MachineIdentifier: hardware fingerprint
├─ IpAddress: captured server-side
└─ EncryptionPublicKey: RSA PEM (enables JWE)
4. License server returns:
├─ JWE token (RSA-OAEP + A256CBC-HS512)
├─ clientId (for heartbeat calls)
└─ audience (stored as [Protected] in consumer)
5. Consumer validates JWE locally using /.well-known/jwks.json + audience check
Discovery Endpoints
> /.well-known/jwks.json
{
"keys": [{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "rdn-lic-2025",
"n": "modulus...",
"e": "AQAB"
}]
}
> /.well-known/license-configuration
{
"issuer": "https://license.reidell.net",
"jwks_uri": ".../.well-known/jwks.json",
"validate_endpoint": ".../validate",
"activate_endpoint": ".../activate",
"heartbeat_endpoint": ".../heartbeat",
"signing_algorithms": ["RS256"],
"encryption_algorithms": ["RSA-OAEP"],
"content_encryption": ["A256CBC-HS512"]
}