Backendless gives you the capability to implement your own License Manager for creating and validating product licenses for your customers. Here, we examine how to put this into action using JavaScript and API services.
In this step-by-step guide, we will demonstrate how to use Backendless API services and features such as data management and Business Logic (Cloud Code) to create and manage product licenses.
Additionally, we will use the Backendless JavaScript SDK and JavaScript Code Generator to build a test application to try out our functions.
Login to your Backendless developer account. If you do not have one, you can register for free at develop.backendless.com.
Create a new Backendless application, or if you already have one, you can use your pre-existing app. The application we’ll use for this article will be called LicenseManager.
First, we should create a new Table in DataService. Let’s call it Licenses. Specify some columns as described in the following image:
As you can see, there are only two additional columns:
We should also set up Roles Permissions for the table to prevent any manipulation with the table records except ServerCode; see the next screenshot:
You can check this out through the REST Console; just go there and try to execute CRUD methods. From the GET method, you will get an empty array of licenses even if there are a few records. From other methods, you will get exceptions.
Our License Manager will work through an API Service. This gives us some security and the ability to write our custom logic as well. We’re going to create that service using JavaScript, which will be running in a NodeJS environment, and we will use some NodeJS modules for implementing our needs.
So let’s create a new API Service and call it LicenseManager. For that, you will go to Business Logic (Cloud Code) -> Coding and create a new JS file with the following content:
class LicenseManager { /** * @param {Number} expiredIn * * @returns {Object} */ async create(expiredIn) { } /** * @param {String} id */ async remove(id) { } /** * @param {String} id * @param {String} token * * @returns {Boolean} */ async verify(id, token) { } } Backendless.ServerCode.addService(LicenseManager)
Click the Save & Deploy button to deploy the service onto the server.
Once it is deployed, you can go to the API Service section and in the left sidebar, you will see the service.
As you can see, now all of the methods have no logic. We will walk through each method of the service in more detail.
As we mentioned before, we will not give the generated license token to the client; we will generate a new, long license token string and the long token will be sent to the client. In the database, we will keep only the encrypted version of the token. NodeJS has its own “crypto” module. It will help us generate the long token and then encrypt the token. So let’s add a few functions for that to the top of our service.
const Crypto = require('crypto')
const SECRET = 'some secret string'
function generateToken(){ return Crypto.randomBytes(128).toString('hex') }
function encryptToken(token, secret) { return Crypto.createHmac('sha256', secret).update(token).digest('hex') }
The SECRET variable contains a secret passphrase. A little bit later, we will move the value to ServiceConfiguration.
As you recall, we are going to keep our licenses in the Licenses table. Specify a dataStore for the table somewhere before the LicensesManager service class.
const LicensesStore = Backendless.Data.of('Licenses')
It looks like we have everything that we need to generate a new license; let’s add some logic to the create method.
/** * @param {Number} expiredIn * * @returns {Object} */ async create(expiredIn) { const token = generateToken() const license = await LicensesStore.save({ token : encryptToken(token, SECRET), expired: Date.now() + (expiredIn * ONE_DAY), }) return { token, id: license.objectId }
As you can see, the method logic is pretty simple and doesn’t require any additional explanation, except for the ONE_DAY variable. In most cases, licenses generated last for a long period, months or days, so the create method expects only one argument: expiredIn (days). For example, if we want to generate a new license for a month, we just pass 30, for a week, just pass 7, and so on.
Just specify the constant at the top of the service’s file.
const ONE_DAY = 24 * 60 * 60 * 1000
Let’s see how our service looks now:
const Crypto = require('crypto') const SECRET = 'some secret string' const ONE_DAY = 24 * 60 * 60 * 1000 function generateToken(){ return Crypto.randomBytes(128).toString('hex') } function encryptToken(token, secret) { return Crypto.createHmac('sha256', secret).update(token).digest('hex') } const LicensesStore = Backendless.Data.of('Licenses') class LicenseManager { /** * @param {Number} expiredIn * * @returns {Object} */ async create(expiredIn) { const token = generateToken() const license = await LicensesStore.save({ token : encryptToken(token, SECRET), expired: Date.now() + (expiredIn * ONE_DAY), }) return { token, id: license.objectId } } /** * @param {String} id */ async remove(id) { } /** * @param {String} id * @param {String} token * * @returns {Boolean} */ async verify(id, token) { } } Backendless.ServerCode.addService(LicenseManager)
Let’s check what we have. Just invoke the method from the Console and take a look at the returned result:
{ "id": "E2CB0ABA-69EF-947D-FF5A-036EED260700", "token": "28530d11319967b1a54d2cbf391e5a379445b282b30ca235f4bdba1fc791a7ba1847293e05c1d6c2b962dcd9ded1631cc52598ec0d337ed8842786da58e768c84fca75794d32a2d1a0c88e0c419abcc5fea5967caa847c7835a88d02de43a7ed72f67fefcc84d1e95129005402d901ddc032762078f1d21283983dc9dee06270" }
And also take a look at the Licenses table:
You probably noticed that the tokens are different. The service returns a longer result than it saved in the table. That’s because the service returns a long license token and then, before we save it in the table, we encrypt it.
In the previous section, we completed a license generation, but we also need to have logic that will check the license. Replace the verify method with the following code:
/** * @param {String} id * @param {String} token * * @returns {Boolean} */ async verify(id, token) { const query = Backendless.DataQueryBuilder .create() .setWhereClause(`objectId = '${id}' AND token = '${encryptToken(token, SECRET)}'`) const license = (await LicensesStore.find(query))[0] if (!license) { throw new Error('License does\'t exist') } if (license.expired < Date.now()) { throw new Error('License is expired.') } return true }
As it was before, the method looks quite simple as well. Here’s what it’s doing:
The method is expecting two arguments, id and token. Then, we try to find a license object with the query objectId = ‘${id}’ AND token = ‘${encryptToken(token)}’. As you can see, before building the query, we transform the token with the encryptToken function as we did in the create method before saving the new license.
Then, if the license doesn’t exist, we just throw an exception. Otherwise, we check if the license isn’t expired yet. In the case that everything is OK, just return true.
Let’s check how it works. We’re going to use the licenseId and licenseToken that we got from the previous section:
{ "id": "E2CB0ABA-69EF-947D-FF5A-036EED260700", "token": "28530d11319967b1a54d2cbf391e5a379445b282b30ca235f4bdba1fc791a7ba1847293e05c1d6c2b962dcd9ded1631cc52598ec0d337ed8842786da58e768c84fca75794d32a2d1a0c88e0c419abcc5fea5967caa847c7835a88d02de43a7ed72f67fefcc84d1e95129005402d901ddc032762078f1d21283983dc9dee06270" }
If you didn’t save that value, don’t worry, just create a new license and use the result for verification.
It works, now let’s change the id or token value and see what result will be returned.
Ok, that works as expected as well, but what about license expiration? Let’s simulate a case where the license is already expired. For that, go to your Data Browser and change the expired value for the license:
Now let’s go back to the API Service and try to verify the license:
In some cases, you may want to remove a license by licenseId. Of course, you can always do that via the Data Browser, but let’s add a simple method for that.
/** * @param {String} id */ async remove(id) { await LicensesStore.bulkDelete(`objectId = '${id}'`) } So, in the end our service should looks like this: const Crypto = require('crypto') const SECRET = 'some secret string' const ONE_DAY = 24 * 60 * 60 * 1000 function generateToken(){ return Crypto.randomBytes(128).toString('hex') } function encryptToken(token, secret) { return Crypto.createHmac('sha256', secret).update(token).digest('hex') } const LicensesStore = Backendless.Data.of('Licenses') class LicenseManager { /** * @param {Number} expiredIn * * @returns {Object} */ async create(expiredIn) { const token = generateToken() const license = await LicensesStore.save({ token : encryptToken(token, SECRET), expired: Date.now() + (expiredIn * ONE_DAY), }) return { token, id: license.objectId } } /** * @param {String} id */ async remove(id) { await LicensesStore.bulkDelete(`objectId = '${id}'`) } /** * @param {String} id * @param {String} token * * @returns {Boolean} */ async verify(id, token) { const query = Backendless.DataQueryBuilder .create() .setWhereClause(`objectId = '${id}' AND token = '${encryptToken(token, SECRET)}'`) const license = (await LicensesStore.find(query))[0] if (!license) { throw new Error('License does\'t exist') } if (license.expired < Date.now()) { throw new Error('License is expired.') } return true } } Backendless.ServerCode.addService(LicenseManager)
As you recall, I promised you that we’d move the SECRET variable into the service configuration. It’s one of the most important features in our API Service. Through that, you can create reusable services or change your configuration without changing code. Just replace the last line where you register the LicenseManager service with the following:
Backendless.ServerCode.addService(LicenseManager, [ { order : 0, displayName : 'Secret Key', name : 'secretKey', type : Backendless.ServerCode.ConfigItems.TYPES.STRING, defaultValue : 'AC0d140cae3f507552c54d2103001308f5', required : true, hint : 'Please specify your Secret Key' } ]
Remove the SECRET variable and replace where it’s used in the create and verify methods with the value from config ‘this.config.secretKey’ and deploy it.
Now you can change the secret key value really easily: open service configuration, change the value and click the “Save” button. But be aware, when you change the secret key, all the existing licenses will be invalid.
This is the most important step because we must deny creating new licenses from outside; otherwise, anyone would able to create a new license and the service becomes useless. A new license must be created based on some action, such as when we receive payment from a customer. Let’s create another service. It will be a billing service, but since this article is about licenses, it will simply be a dummy service that we will call FooBar service. Inside the service method, we will call our LicenseManager service:
class FooBar { /** * @returns {Object} */ async createLicense() { console.log('here we should do some work before create a new license') return (await Backendless.Request.post(`http://api.backendless.com/{YOUR_APP_ID}/{SERVER_CODE_API_KEY}/services/LicenseManager/create`).set('Content-Type', 'application/json').send(JSON.stringify(10))) } }
Ok, check this out: go to API Services and invoke the service.
It works! But one important thing remains: the LicensesManager.create method is still insecure because we can invoke it directly. Let’s fix that. We will deny access to the create method for everyone except ServerCode.
As you can see, now it’s impossible to invoke the service method directly, but it works from the FooBar service.
When a license becomes expired, we don’t need to keep that license in the Licenses table, so let’s create a new timer for removing expired licenses each hour.
Replace the execute function with the following code:
async execute(req){ await LicensesStore.bulkDelete(`expired < ${Date.now()}`) }
Now deploy the changes:
That’s all, every expired license will be removed automatically.
Most likely, you will want to check if the current license is still valid. This is not hard to implement; we need just to call the verify method of our LicensesManager service. As you recall, we only deny invocation from the outside for the create method for security reasons, but the verify method is still available for invoking from outside. Backendless has the ability to generate code for services by doing what’s described in the next screenshot:
It will generate a JS project with the code of the API Service. Open the html file in your browser and you will see something like this:
In the browser’s console, you can play with all the methods of the service. Let’s generate a new license, remember licenseId and licenseToken, and check the license from the client.
const license = { "id": "YOUR_LICENSE_ID", "token": "YOUR_LICENSE_TOKEN" } LicenseManager.verify(license.id, license.token).then(console.log, console.error)
Today we have done a simple API service for managing licenses. It isn’t too difficult to implement and can be quite helpful in many cases. We also touched on Permissions, learned how to create Timers, and showed what Service Configurations are.
Thanks for reading, I hope you’ve enjoyed it and see you soon.