Choosing a Licensing Model
When first starting out, choosing a licensing model can be a bit daunting. There are so many choices, how do you choose which one is best for your business? A quick Google search is likely to confuse you more than educate you, thanks to a plethora of enterprise jargon and buzzwords. Today, we'll take an in-depth look at licensing from a business perspective so that you can make an educated decision on what type of model is best for you and your business.
Licensing terminology
Below is a non-exhaustive reference for some of the terminology used in this guide and throughout the licensing space. Feel free to refer back to this throughout the guide, but we'll try to keep things as straight forward as possible, regardless. If you're a developer implementing Keygen, this list will also provide you with a good overview of all of our resources.
- Users are a particular resource within Keygen that represents a user of your software, allowing them to authenticate with Keygen's API and manage the licenses and machines which are associated with their user resource.
- Licenses are a particular resource within Keygen that, in their simplest form,
represent a permission i.e. you give the license owner permission to do
x
within your software, whetherx
is to use your software as whole, or only a particular feature your software offers. - Policies are resources within Keygen that define how individual licenses should behave, and what rules those licenses must follow. A license is always an implementation of a policy i.e. you cannot create a license without first having a policy that defines its behavior.
- Machines are a device or node that is associated with a license within Keygen. When creating a machine and associating it with a license, you must specify a unique string called a fingerprint which the machine is identified by.
- A fingerprint is a unique string that identifies a particular machine. There are many ways to create a fingerprint, but most use a combination of device-specific components such as MAC address, HDD serial number, OS, etc., but it could also simply be a domain name (e.g. when licensing a WordPress plugin), or even a file you store locally on their machine containing the unique string.
Licensing goals
Before we go any further, we should take a step back and think about why we want to implement licensing and what we want to accomplish through licensing. Only then can we begin to decide what licensing model will best accomplish those goals.
- I want to limit access of my software to only licensed users so that I can make sure all of my users are paying customers.
- I want to offer timed licenses to each of my users so that they must renew their license at the end of the expiry.
- I want to offer a limited trial version of my software to users so that users can try out my software before purchasing.
- I want to offer in-app purchases (or a "freemium" model) so that users can use a "basic" version of my software before purchasing "pro" features.
- I want to limit access of my software to a single machine so that customers have to purchase a new license for each additional machine.
- I want to limit access of my software to only
x
number of machines so that a license cannot be used on unlimited machines. - I want to offer a limited supply of "special" licenses so that I can run a promotional campaign with my audience.
- I want to license individual features of my software to users so that I can upsell premium features.
- I want to require periodic "check-ins" for licenses to remain valid so that I can offer offline support but still require a periodic internet connection.
- I want each license to have a usage limit so that I can offer plans based on usage of a particular feature.
- I want to cryptographically sign or encrypt license keys so that I can validate license keys when the customer is offline.
Licensing models
You may create resources directly using Keygen's API, or you can use your account dashboard to do so instead. The policies below use default attributes where possible, so be sure to review the Policy resource's default attributes.
Table of Contents
- I want to limit access of my software to only licensed users
- I want to offer timed licenses to each of my users
- I want to offer a limited trial version of my software to unlicensed users
- I want to offer a premium version of my free software
- I want to limit access of my software to a single machine
- I want to limit access of my software to only
x
number of machines - I want to offer a limited supply of "special" licenses
- I want to license individual features of my software to users
- I want to require periodic "check-in" for licenses to remain valid
- I want each license to have a usage limit
- I want to cryptographically sign or encrypt license keys
I want to limit access of my software to only licensed users
Keeping things simple. Below is a policy which implements the bare-minimum to implement a licensing system for your software. It will be valid on an unlimited number of machines.
Here's what that Policy would look like (with everything else left to defaults):
{
data: {
type: "policies",
attributes: {
name: "Pro License"
}
}
}
Licenses that implement this policy will never expire and will be valid on an unlimited number of machines. The only way for a license implementing this policy to fail validation is for it to be suspended or revoked.
I want to offer timed licenses to each of my users
To offer licenses which have an expiration date, we'll need to create a policy similar
to the above one, but one which also has a value for the duration
attribute.
And here's what that would look like (everything else left to defaults),
{
data: {
type: "policies",
attributes: {
name: "Pro License",
duration: 1209600 // 2 weeks
}
}
}
The duration
attribute is how long, in seconds, the license is valid for from the
license's creation timestamp, e.g. a license is created at epoch 1503520001
, so its
expiration will be 1503520001 + 1209600
, which is 09/06/2017 at 8:26pm UTC.
When a license's expiration gets close, we will fire off an license.expiring-soon
webhook event so that you can notify your customer and even
automate renewing the license server-side.
I want to offer a limited trial version of my software to unlicensed users
This is similar to the above implementation, but instead of only having a single policy, we'll have 2. The first one will be our "full" policy, while the second will be our "trial" policy.
Here's what those Policies would look like (with everything else left to defaults):
// Full license policy
{
data: {
type: "policies",
attributes: {
name: "Full License",
duration: 31557600 // 1 year
}
}
}
// Trial license policy
{
data: {
type: "policies",
attributes: {
name: "Trial License",
duration: 1209600 // 2 weeks
}
}
}
The only difference is the duration
of each policy, i.e. the "trial" version will
expire in 2 weeks, while the "full" version will be valid for 1 year.
I want to offer a premium version of my free software
This particular model is usually referred to as "freemium" or "in-app purchases", and it's one of the models that Keygen was originally built around. For reference, we even have an example application which implements a similar form of this type of licensing.
And here's a representation of the 2 policies (with other attributes left to defaults),
// Base license policy
{
data: {
type: "policies",
attributes: {
name: "Base License"
}
}
}
// Premium license policy
{
data: {
type: "policies",
attributes: {
name: "Premium License"
}
}
}
You can then store each policy's ID within your product to determine if the current user is allowed to use the "premium" features of your product. You can find a policy's ID by viewing the policy from your account dashboard.
View example implementation
const fetch = require("node-fetch")
// Policies representing our product editions. You can get this information
// from your dashboard: https://app.keygen.sh/policies
const KEYGEN_PREMIUM_POLICY = "f4eb56fa-cd4d-4060-b9fe-66fb70d875d9"
const KEYGEN_BASE_POLICY = "35900395-c0e6-4fda-b174-a440ef58dd12"
const PRIVILEGES = {
premium: false,
base: false
}
const licenses = [/* fetch user's licenses */]
for (let license of licenses) {
const { id, relationships: { policy: { data: policy } } } = license
switch (policy.id) {
case KEYGEN_PREMIUM_POLICY: {
const res = await fetch(`https://api.keygen.sh/v1/accounts/{ACCOUNT}/licenses/${id}/actions/validate`, {
method: "POST",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
const { meta, errors } = await res.json()
if (errors) {
continue
}
PRIVILEGES.premium = meta.valid
break
}
case KEYGEN_BASE_POLICY: {
const res = await fetch(`https://api.keygen.sh/v1/accounts/{ACCOUNT}/licenses/${id}/actions/validate`, {
method: "POST",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
const { meta, errors } = await res.json()
if (errors) {
continue
}
PRIVILEGES.base = meta.valid
break
}
}
}
// … later in the product
if (PRIVILEGES.premium) {
// … do something
}
I want to limit access of my software to a single machine
To limit access to a single machine (also called node-locked licensing), we'll take advantage of a few other policy attributes. Node-locked is a fancy way to say "I want a license to only be valid on a single machine" i.e. you "lock" the license to a particular machine resource.
Here's what that Policy would look like (with everything else left to defaults):
{
data: {
type: "policies",
attributes: {
name: "Node-Locked License",
requireFingerprintScope: true, // All validations will require a machine fingerprint
maxMachines: 1, // Licenses should only be associated with a single machine
floating: false, // We do not want to allow more than 1 machine at a time
concurrent: false, // We do not want them to be able to exceed our 1 machine limit
strict: true // Invalidate licenses which do not meet machine requirements
}
}
}
In order for a license to pass validation, it must be associated with a single machine—no more, no less. In addition, all validation requests must contain a machine fingerprint scope, which will be used to determine whether or not the machine is valid for the given license. Whenever a user goes over their single machine limit, their license will be invalidated until the additional machines are removed.
I want to limit access of my software to only x
number of machines
Similar to implementing "node-locked" licenses, we can also implement "floating" licenses. Floating is a term used to describe the opposite of "node-locked" i.e. a "floating" license is valid across multiple machines, but only up to a maximum machine count.
Here's what that would look like,
{
data: {
type: "policies",
attributes: {
name: "Floating License",
requireFingerprintScope: true, // All validations will require a machine fingerprint
maxMachines: 5, // Licenses can be associated to a maximum of 5 machines
floating: true, // We want to allow more than 1 machine at a time
strict: true // Invalidate licenses which do not meet machine requirements
}
}
}
In order for a license to pass validation, it must be associated with at least 1 machine, but no more than 5 machines total. In addition, all validation requests must contain a machine fingerprint scope, which will be used to determine whether or not the machine is valid for the given license. Whenever a user goes over their machine limit, their license will be invalidated until the additional machines are removed.
This type of policy/licensing model can be used to implement a "concurrent" or
"per-seat" licensing system, e.g. where you only allow n
machines to be active
at any given time. If you do not want the user to be able to exceed the machine
limit (even though it would still invaliate their license), you can set
concurrent = false
.
I want to offer a limited supply of "special" licenses
In order to implement a "limited supply" of licenses, we'll need to create a pooled policy. Before we start, let's go over a little bit of terminology:
- A pooled policy is a term used within Keygen to describe a policy which has a limited amount of pre-determined keys available for use. Once a policy's pool is depleted, you must add additional keys in order to continue to create new licenses.
- Keys are a special resource within Keygen that represent an unused key within a particular pooled policy's key "pool". Keys are not valid "license keys" until a license resource is created from the pooled policy. When a license is created which implements a pooled policy, a key is taken from the top of the pool and is used for the new license.
Below is a representation of a pooled policy,
{
data: {
type: "policies",
attributes: {
name: "Limited Edition License",
usePool: true // Pull keys from the policy's pool when creating licenses
}
}
}
Now, if we tried to create a license which implements this policy right away, we would receive an error saying that our policy's pool is empty. So before we can allow license creation, we need to add keys to our policy's pool. Once a policy's pool is depleted, you must add additional keys in order to continue to create new licenses.
I want to license individual features of my software to users
This particular model is called "feature licensing", and it's one of the models that Keygen was originally built around. For reference, we even have an example application which implements feature licensing.
There are 2 different ways that you could implement this model:
- Create feature licenses per-customer i.e. if customer A has access to feature X and feature Y, they should get a license for Y and another license for X, a "feature license." If customer B has access to only feature Y, they should only have a license for Y. Each feature license would have its own policy, referred to as a "feature policy." This has the side-effect of a high number of licenses, each with their own validity rules, which may not be what you’re looking for and may be harder to manage. If you utilize our identity management features (i.e. user accounts), you can make sure all of a customer’s licenses are associated with their user account, which can be queried as-needed.
- Create a single license per-user and store the allowed features within the license’s metadata attribute i.e.
{ "featureX": true, "featureY": false }
. You can store arbitrary data within the metadata attribute, so it may be a better fit for your use case. This has the benefit of a lower number of licenses per-customer, since all the feature entitlement data is consolidated into a single license resource.
Going with the first one, feature policies would end up looking like,
// Feature "X"
{
data: {
type: "policies",
attributes: {
name: "Feature X"
}
}
}
// Feature "Y"
{
data: {
type: "policies",
attributes: {
name: "Feature Y"
}
}
}
// Feature "Z"
{
data: {
type: "policies",
attributes: {
name: "Feature Z"
}
}
}
You can then store each policy's ID within your product to determine if the current user is allowed to use each particular feature of your product. You can find a policy's ID by viewing the policy from your account dashboard.
View example implementation
const fetch = require("node-fetch")
// Policies representing our product's features. You can get this information
// from your dashboard: https://app.keygen.sh/policies
const KEYGEN_FEATURE_X = "aac4905c-84d0-41a3-af6e-1026e28c04d3"
const KEYGEN_FEATURE_Y = "dd025847-42fb-49b0-b898-80c34d7734b4"
const KEYGEN_FEATURE_Z = "b6a5ae11-ec60-4ecd-9902-ae48a1077623"
const ALLOWED_FEATURES = {
[KEYGEN_FEATURE_X]: false,
[KEYGEN_FEATURE_Y]: false,
[KEYGEN_FEATURE_Z]: false
}
const licenses = [/* fetch user's licenses */]
for (let license of licenses) {
const { id, relationships: { policy: { data: policy } } } = license
switch (policy.id) {
case KEYGEN_FEATURE_X:
case KEYGEN_FEATURE_Y:
case KEYGEN_FEATURE_Z: {
// Validate the current license
const res = await fetch(`https://api.keygen.sh/v1/accounts/{ACCOUNT}/licenses/${id}/actions/validate`, {
method: "POST",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
const { meta, errors } = await res.json()
if (errors) {
continue
}
ALLOWED_FEATURES[policy.id] = meta.valid
break
}
default: {
// This version of our product doesn't use this policy so it's okay to skip
// it (so we can implement new features without breaking old versions)
break
}
}
}
// … later in the product
if (ALLOWED_FEATURES[KEYGEN_FEATURE_X]) {
// … do something
}
I want to require periodic "check-in" for licenses to remain valid
You can require licenses to periodically "check-in" using the check-in action. Below is a policy which requires licenses to check in every 2 weeks in order to remain valid. Check-in policies are great for allowing offline usage of your product, but still requiring a periodic "check-in" to validate licenses e.g. to ensure they aren't expired or suspended.
Here's that policy:
{
data: {
type: "policies",
attributes: {
name: "Check-In License",
requireCheckIn: true, // Require periodic check-ins
checkInInterval: "week", // Can be: "day", "week", "month" or "year"
checkInIntervalCount: 2 // How many intervals e.g. every 2 "weeks"
}
}
}
When a license's check-in gets close, we will fire off a license.check-in-required-soon
webhook event so that you can notify your customer to
let them know that they need to connect to the internet soon and validate
their license. Likewise, when a license becomes overdue, we will send a
license.check-in-overdue
event.
One major caveat to this system is that the offline machine doesn't have access to Keygen's servers to check if it's overdue, which poses a challenge when it comes to enforcing the check-in requirement. For example, when a license becomes overdue, you will receive a webhook event, but the offline machine will never see that, since it has no way of communicating with the outside world over the internet. Because of this, they could continue using your product, even when you don't want them to.
One solution would be to store the allowed offline usage duration within the license's metadata attribute. For example, the following:
{
offlineUsageDuration: 7 days
}
This metadata value will be included within all validation responses for the license, under
the data.attributes.metadata
property. This value, along with the value from meta.ts
, which
is the timestamp at which the license was validated, can be used to determine when the offline
machine will be required to connect to the internet.
Succinctly, such a system could resemble the following:
- When the machine is connected to the internet, you perform a license validation request.
- You store the response of that validation request locally, along with the crytographic signature
of the data (available in the
X-Signature
header), somewhere on the machine (e.g. a secure registry, the local filesystem, etc.) - When the machine is offline, you query the stored data, along with the signature.
- You verify that the data's signature is valid using your account's public key.
- You utilize the offline usage duration that you stored within the license's metadata, and the timestamp of the last validation, to calculate the time at which an internet connection will be required.
You can then calculate the date at which an internet connection is required like so:
if (current timestamp < validation timestamp + offline usage duration) {
// Allow usage - they are within the allotted offline duration
} else {
// The machine needs to be connected to the internet
}
Since the locally stored data is stored alongside a cryptographic signature, you can rest assured that your licensing system cannot easily be tampered with by modifying the local data, since that would invalidate the signature.
With this system, you could even forego the check-in action entirely, since the bulk
of the logic could be handled within your product itself. (You could simply utilize
the nextCheckIn
attribute, instead of performing the calculation manually using
metadata, but that decision is left up to you.)
I want each license to have a usage limit
You can set up license policies to enforce a usage limit by utilizing the
maxUses
attribute, along with utilizing a license's usage-related actions,
such as increment-usage and
decrement-usage.
Here's what that policy would look like,
{
data: {
type: "policies",
attributes: {
name: "Usage License",
maxUses: 25
}
}
}
Then you would utilize it by incrementing the license's uses
attribute
through its increment-usage action when usage occurs (you choose when to
increment usage),
View example implementation
const fetch = require("node-fetch")
const res = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/licenses/b18e3f3a-330c-4d8d-ae2e-014db21fa827/actions/increment-usage", {
method: "POST",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
const { data, errors } = await res.json()
if (errors) {
if (res.status === 422) {
// … handle case where they've reached their usage limit
}
return
}
// … allow usage
You can reset a license's usage count from your admin dashboard, or via the licensing API.
I want to cryptographically sign or encrypt license keys
To allow offline license key validation, you can cryptographically sign or encrypt license keys, which can be verfied using your account's public key (available in your Dashboard's settings page).
Here's a policy which signs license keys using RSA's PKCS1-PSS scheme:
{
data: {
type: "policies",
attributes: {
name: "Cryptographically Signed License",
scheme: "RSA_2048_PKCS1_PSS_SIGN"
}
}
}
Using the RSA_2048_PKCS1_PSS_SIGN
scheme, when a license is created which
implements this policy, the provided key will be cryptographically signed
and that signature will be appended onto the end of the key. You can then
cryptographically verify the entire key in offline environments.
The resulting license key will resemble the following:
eyJmb28iOiJiYXIiLCJiYXoiOiJxdXoiLCJyYW5kIjpbNDgsMTAxLDU2LDk5LDQ5LDQ5LDk3LDUzLDU3LDU0LDQ5LDEwMSw1MCw0OSw1Miw1MCwxMDIsMTAyLDQ5LDU0LDUyLDEwMiw1Miw1NSw1Niw1MCw5Nyw0OCw0OSw1Myw1MywxMDFdfQ==.Dqw0N_14L0v-a-cQPkTM_X6_vN-tSoEWEt2Zu5SreeS_-Zn6Fsyr2EwpnOXWkWHhmcPJWSfboCrOjh73y-Q9I3aLbpTg5M_9bQmt2PjwG0l7A7T2PUreAA-8xftK0-_quQchsv73JDhrst8O1oh2dHBs6fB1EPXzKKCyIXKWIniHjEP-nqGGYXUzPQjoMI7JQCySZKtXmgV4rVRoY1E9kxywlc0Lia9OfHH2uDnBPjIpR_4U_QFLzoUKZnG5qmTC9PtgGbdsN5e01hk53ZvDA9sEdzUerk_34O6qBy203mt_4NvVbegIcS49IzyGtfn_a4OLbEGnz-gOnS2ee0Js6g==
The key contains a base64 encoded version of the key specified at the time
of creation, along with a cryptographic signature, delimited by the .
character. You can split the key by the .
character to verify its
contents using RSA's signature verification methods.
The exact behavior of each scheme
is detailed in our API reference.
View example implementation
const crypto = require('crypto')
const {
KEYGEN_PUBLIC_KEY,
LICENSE_KEY
} = process.env
// Extract key and signature from the license key payload
const [encodedKey, encodedSignature] = LICENSE_KEY.split('.')
// Decode the base64 encoded key
const key = Buffer.from(encodedKey, 'base64').toString()
// Verify the signature
const verifier = crypto.createVerify('sha256')
verifier.write(key)
verifier.end()
const ok = verifier.verify({ key: KEYGEN_PUBLIC_KEY, padding: crypto.constants.RSA_PKCS1_PSS_PADDING }, encodedSignature, 'base64')
if (ok) {
console.log('License key is valid!')
} else {
console.log('License key is invalid!')
}
Don't see the licensing model you're looking for? Reach out and let us know, and we'll get it added here plus answer any questions you have about implementation.