For my Print My Blog Pro plugin I’ve had an interesting challenge: I needed to validate Freemius licenses outside of the plugin. This is quite doable using Freemius’s REST API, including authentication, but it’s a bit involved so I thought I’d share how I’m doing it.
What is the PRoblem THis Solves?
Let’s say you have a WordPress plugin that’s integrated with Freemius. Freemius takes care of processing the user’s payment, and mostly your plugin’s code communicates directly with Freemius’s servers to verify the user’s license to use the software is still valid. That’s pretty straightforward.
But let’s say your code needs to do something that can’t be done in a plugin (like use software that can’t fit into a WordPress plugin, use your private API keys you can’t put in share in the plugin, or run some code you just want to keep private.) In that case, the users’ sites, running your plugin, need to send messages to your website, and your website needs to figure out:
- If the site making the request has a valid license (ie, is the requesting site authorized?)
- If the site making the request is the owner of the license (ie, is the request authentic?)
For example, Print My Blog is a WordPress plugin which I’m integrating with Freemius which is primarily for making PDFs from WordPress posts. The free version just used the user’s browser to make the PDF, but the Pro version instead sends the PDF-creation data to my website, printmy.blog, which forwards the request onto another service called DocRaptor. I pay an expense every time a user generates a PDF via DocRaptor, so only paying users get to do it. In other words, printmy.blog needs to validate any incoming requests to make sure they’re from a user with a valid license key.
Who Are The Major Players?
There are three primary machines at play:
- The WordPress site with the plugin installed. I’ll call this the user’s site
- The website (not necessarily running WordPress, but mine does) that receives a request from the user’s site, and needs to validate a license key. I’ll call this the license-key validating site.
- Freemius’ website, which knows which license keys are valid and which aren’t. I’ll just call this Freemius.
I suppose I could include DocRaptor, the PDF-creation service printmy.blog uses, but I don’t think that’s relevant to today’s topic.

What Tools Do We Have Available?
Freemius and WordPress provide all the tools you need, both on the user’s site and the license key validation site (the latter doesn’t have to be WordPress, but that’s what I used).
In the WordPress plugin, I used Freemius WordPress API SDK. That facilitates communication with Freemius from the WordPress plugin.
On the license-key validation site, I also use the Freemius PHP SDK, which facilitates making requests to Freemius’ REST API. I also used the WordPress REST API on this license-key validating site to receive requests from users’ sites (but I could have instead used WP AJAX.)
Another important tool I ended up using was the private key Freemius assigns each site. Only the user’s site, and Freemius, know each site’s private key. But, the license key validating site can request it from Freemius. This will be key in authenticating the communication.
Pitfalls to Avoid
I went through multiple iterations of this code. I started off a script I got from Daryll Doyle, saw some problems, and then improved it a few times.
The first pitfall to avoid was forgetting that Freemius license keys aren’t very private. While you probably won’t find Freemius license keys on other websites or in source code, they’re very easy to guess. That’s because they’re actually just numbers, like 123456. So if you had patience, you could just guess them (or write a program to guess them for you, ie brute force guess them.)
The second pitfall was that while the site’s private key can be used to “sign” a request, you need to know what algorithm the private key is implementing, in order to verify that signature using the public key. So I couldn’t use the private and public keys in the usual way.
Lastly, I wasn’t always directly sending requests from the user’s site to the license-key validating site: sometimes the user’s site was loading some Javascript that sent the request to the license-key validating site. This meant that I couldn’t pass the user’s private key to the client-side Javascript, because the user could use a web inspector to see it, and that wouldn’t keep the private key very private anymore.
User’s Site Code (WOrdPress Plugin)
So let’s jump into the code, first what’s on the user’s site.
I first use the Freemius WordPress SDK to get the user’s license key, site ID, and private key. Here’s the relevant bits of code:
// use Freemius' PHP SDK and the site's Freemius function, in my case pmb_fs(), to get the site's
// license key, site ID, and secret key
$license_id = pmb_fs()->_get_license()->id;
$install_id = pmb_fs()->get_site()->id;
$site_private_key = pmb_fs()->get_site()->secret_key;
Next, I need to not only send the license key (or license ID, because it’s just a number) to the license-key validating site, but I also need to somehow prove to the license-key validating site that I am the owner of that license key and the site it corresponds to.
The way I do that is this: instead of sending the private key, I send a hash of the private key and a timestamp. This way the private key is never exposed, only a hash of it is ever sent. And if that hash is ever intercepted, it will only be valid until the timestamp changes.
// create the signature that verifies we own this license and install.
$nonce = date('Y-m-d');
$pk_hash = hash('sha512', $site_private_key . '|' . $nonce);
$authentication_string = base64_encode($pk_hash . '|' . $nonce);
This is a bit strange, I admit. I’m using $nonce
(the timestamp) twice: both in creating the hash, and then I only base64 encode it and add it to the $authentication_string
. What’s going on here?
My objective is that I want the hash to expire, so that if it somehow got leaked on the internet, it would only be usable for a short time frame. So to do that, the hash is built from the $nonce
, not just the private key.
If the request were received at the exact same time it was sent, that’s all I would need, because the server also knows what time it is. But the fact is that sometimes requests are sent a second or two before they’re received, and so the timestamp at the time it’s received could be different. So I also include include the timestamp $nonce
(just base64 encode it, which does nothing for security because it’s trivial to decode it using base64_decode
). This way the server will also know what date was used to send the request, and can decide if it’s in an acceptable date range.
Anyways, after all that, I use WordPress’ wp_remote_get
to send a request to the license-key validating site, and send the $authentication_string
in the HTTP Authorization header, like so:
// send a request to the license key validating server
$url = $this->getCentralUrl() . '/licenses/' . $license_id . '/installs/' . $install_id . '/credits';
$response = wp_remote_get(
$url,
[
'headers' => [
'Authorization' => $authentication_string
]
]
);
So, all of that put together looks like this:
// use Freemius' PHP SDK and the site's Freemius function, in my case pmb_fs(), to get the site's
// license key, site ID, and secret key
$license_id = pmb_fs()->_get_license()->id;
$install_id = pmb_fs()->get_site()->id;
$site_private_key = pmb_fs()->get_site()->secret_key;
// create the signature that verifies we own this license and install.
$nonce = date('Y-m-d');
$pk_hash = hash('sha512', $site_private_key . '|' . $nonce);
$authentication_string = base64_encode($pk_hash . '|' . $nonce);
// send a request to the license key validating server
$url = $this->getCentralUrl() . '/licenses/' . $license_id . '/installs/' . $install_id . '/credits';
$response = wp_remote_get(
$url,
[
'headers' => [
'Authorization' => $authentication_string
]
]
);
Note about the Javascript code: I’d rather not dive into all that, but suffice it to say that I passed it the $license_id
, $install_id
, and $authentication_string
using wp_localize_script
. Notice that I never passed the site’s private key to the Javascript code, only the authentication string which served as a signature. So it’s possible that authentication string could get exposed, but it’s only usable for a short amount of time.
License-Key Validating Server Code
The license-key validating server will need to watch for the incoming request, request the data it needs from Freemius, and verify the request is authentic and authorized.
To start off, I first register a REST API endpoint (in my case, it’s one for checking how many credits are left on a license key). It looks like this:
register_rest_route(
'pmb/v1',
'licenses/(?P<license_id>[^\/]+)/installs/(?P<install_id>[^\/]+)/credits',
[
'callback' => 'creditCheck',
'permission_callback' => '__return_true',
'methods' => WP_REST_Server::READABLE,
'args' => [
'license_id' => [
'description' => __('Freemius License ID', 'print-my-blog'),
'type' => 'string'
],
'install_id' => [
'description' => __('Freemius Install ID', 'print-my-blog'),
'type' => 'string'
]
]
]
);
On the callback creditCheck
, I first want to ensure the site ID provided corresponds to the correct license ID, then I want to authenticate the request. To do that, I use the “install ID” (on the user’s site I called it the “site ID”… to be honest they’re a bit confusingly interchangeable) to request details about that site from Freemius’s server, specifically what license ID it’s for and what’s its private key.
$api = new Freemius_Api(
FS_SCOPE,
FS_DEV_ID,
FS_PUBLIC,
FS_SECRET,
FS_SANDBOX
);
// Get all products.
// on success, looks like the response from https://freemius.docs.apiary.io/#reference/installs-/-a.k.a.-sites/install/retrieve-install
$install_id = $request->get_param('install_id')
$result = $api->Api( sprintf( 'plugins/%s/installs/%s.json', FS_PLUGIN_ID, $install_id) );
// Check for error
if ( property_exists( $result, 'error' ) ) {
throw new Freemius_Exception(json_decode(json_encode($result),true));
}
// Verify the site ID they sent is for the license ID we want to validate
if($result->license_id != $license_id){
throw new Exception(sprintf(__('The site %1$s is for license %2$s, not %3$s', 'print-my-blog'), $install_id, $result->license_id, $license_id));
)
// Ok, now we know this is the right secret key for the site and license!
$secret_key = $result->secret_key;
Then the license-key validating site will take the Authorization header and verify it was created by the site using this secret key. It will also verify it was generated at the time it claims to have been generated.
// get the authorization string from the WP_REST_Request
$authorization_string= $request->get_header('Authorization');
// Split it up into the hash and the timestamp
$authorization_code_decoded = base64_decode( $authorization_string);
$hash_and_nonce = explode('|', $authorization_code_decoded);
if( ! is_array($hash_and_nonce) || count($hash_and_nonce) !== 2){
throw new Exception(__('The site signature was improperly formed. Please contact support', 'print-my-blog'));
}
list($hash, $nonce) = $hash_and_nonce;
// determine if the timestamp is recent enough. Right now we're accepting anything within the last 25 hours
$nonce_timestamp = strtotime($nonce);
$now = current_time('timestamp');
if($now - $nonce_timestamp > (HOUR_IN_SECONDS * 25) ){
// nonce is too old
throw new Exception(__('The nonce provided is too old. Please try the request again', 'print-my-blog'));
}
// calculate what we think the hash should be, using the private key from the site they claimed, and the time they claimed
$our_hash = hash('sha512', $site_pk . '|' . $nonce);
if($hash !== $our_hash){
throw new Exception(__('The signature does not appear to be from the specified site. Please contact support', 'print-my-blog'));
}
// now they're authenticated!
After that, we know they own the site ID they claim they did, that it’s for the license key they’re using, and the request was generated at the time they claimed. Now it’s time to check the license is still valid.
So this is how we check if the license is still authorized (this is just like Daryll Doyle’s code snippet)
// Init SDK.
$api = new Freemius_Api(
FS_SCOPE,
FS_DEV_ID,
FS_PUBLIC,
FS_SECRET,
FS_SANDBOX
);
// Get all products.
// on success, looks like the response from https://freemius.docs.apiary.io/#reference/licenses/license/get-license
$result = $api->Api( sprintf( 'plugins/%s/licenses/%s.json', FS_PLUGIN_ID, $request->get_param('install_id')) );
if ( property_exists( $result, 'error' ) ) {
throw new Freemius_Exception(json_decode(json_encode($result),true));
} else {
// check they're license is still valid
try {
$now = new DateTime();
$expiration = new DateTime( $result->expiration );
if ( $expiration >= $now ) {
// license is still valid!
return true;
}
return false;
} catch ( Exception $exception ) {
return false;
}
}
So after all that, we know the license key is still valid. And we’ve verified it was sent by a site using that license.
Conclusion
So what did we do? Our license-key validating server not only checked that the user’s site has a valid license, but also that the request actually came from a site using that license key. And the signature/authorization string was produced quite recently, so even if it got exposed, it’s only usable for as long a period of time as your license-key validating server allows it.
Questions or see any problems with the above? Please let me know!
One thought on “How to Validate Freemius Licenses Outside of a Plugin”