Rdn.License

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

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)

entity-relationships

> 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

5

List, Read, Create, Update, Delete

Product Features

5

List, Read, Create, Update, Delete

Pricing Tiers

5

List, Read, Create, Update, Delete

Customers

5

List, Read, Create, Update, Delete

License Keys

9

List, Read, Create, Delete, Revoke, Validate, Regenerate, Audit, Heartbeat

Activations

3

List, Activate, Deactivate

Pending Claims

4

List, Read, Delete, ResendInvite

System Logs

2

List, Update

Self-Service Registration

1

Register

Customer visits /register with product and tier in query params. A PendingLicenseClaim is created with Pending status.

2

Identity Invite

The License API calls Rdn.Identity to create the user account and send an email invite with setup instructions.

3

Set Password

Customer receives the email, follows the link to Rdn.Identity, and sets their password to activate their account.

4

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

activation-sequence

> 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

> /.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"]

}