Securing REST endpoints with API keys
Goal: Implement a simple REST-Service and secure it via API keys.
You can download the whole Kickstarter-based sample application from our GitLab repository: um-public/tutorial-securing-rest-apis
This tutorial requires UM version 7.56 or newer.
Start a new Kickstarter project
First, choose a Java-style package name for your new plugin – e.g. de.acme.tutorial
. (See also ACME Corporation)
Please note that com...
, net...
and org...
denote reserved namespaces and can not be used as top-level package names.
Second, use UM-Kickstarter to begin a new UM project:
umkickstarter -n de.acme.tutorial
cd umkickstarter
gradle setup run
Implement REST controller
Create an .mjs
file in your plugin’s rest/
folder:
cmsbs-conf/cse/plugins/de.acme.tutorial/rest/api.mjs
/// <reference path="../../../.vscode.js"/>
import { RouterBuilder } from "@de.pinuts.apirouter/shared/routing.es6";
/**
* @param {HttpRequest} req
* @param {HttpResponse} res
*/
const listEntries = (req, res) => {
res.send({
data: UM.query('entrytype="customer"')
.page(100)
.map(e => e.toSeedJSON())
});
}
/**
* @param {HttpRequest} req
* @param {HttpResponse} res
*/
const createEntry = (req, res) => {
const e = UM.addEntry(null, 'customer');
e.set('firstname', req.jsonRequestBody.firstname);
e.set('lastname', req.jsonRequestBody.lastname);
e.set('email', req.jsonRequestBody.email);
UM.commitEntries();
res.send({
data: e.toSeedJSON()
}, 201);
}
de.acme.tutorial.apiController = new RouterBuilder()
.protectFromCaching()
.get('/entries', listEntries)
.post('/entries', createEntry)
.build();
Restart your UM instance:
gradle run
Invoke REST service
Try to invoke the new REST service in your browser:
http://localhost:8080/cmsbs/rest/de.acme.tutorial.api/entries
Open the UM’s backoffice UI and manually create some Customer entries. Invoke the above REST service again. You should be presented with a JSON formatted list of your newly created customer entries.
Try to create a new customer entry by performing a POST request via curl:
Secure your REST endpoint
As you may have noticed, there is currently no authentication required, so virtually everybody could call that very endpoint and create or query entries.
Since UM 7.56 you can create and manage API keys in the UM’s backoffice and use them to authenticate (and authorize) callers of your APIs.
Modify your RouterBuilder from above and retry the before mentioned GET and POST requests:
By calling .requireApiKey()
you tell the RouterBuilder to expect an Authorization request header containing valid Basic Auth credentials.
Try retrieving the list of entries again:
This will yield a 401 Unauthorized!
Create API key
Go to http://localhost:8080/cmsbs/Custom/s_/de.pinuts.cmsbs.apitoken/index and create a new API key:
Click Create and copy Key and Secret from the following screen…
…and use them as USERNAME and PASSWORD when invoking your REST endpoint:
Since the secret key is stored as a hash digest in the database, it cannot be viewed again at a later time.
If you should happen to loose your secret key, simply delete the API key and create a new one.
Add more specific authorization checks
If we want to be more specific as to what permissions are required to call your endpoint, we can introduce and check for project specific permissions by adding them to our cmsbs-conf/cse/plugins/de.acme.tutorial/plugin.desc.json
file:
This will introduce the following new API permissions that we can assign to our API key:
de.acme.tutorial:ListEntries
de.acme.tutorial:CreateEntry
Restart your UM instance and visit http://localhost:8080/cmsbs/Custom/s_/de.pinuts.cmsbs.apitoken/index again to specifically assign the newly added de.acme.tutorial.ListEntries
permission to your API key:
Now, modify your RouterBuilder once again to check for the appropriate permissions in both of the routes:
With the permissions from the screenshot above, you should be able to retrieve the entry list…
… but should be denied to create a new entry: