Credit: IBM
Safeguard your application with another line of defense
SQL databases have fine-grained access control. (For an example, see the
article entitled “Row and column access control” in the IBM Knowledge Center.) In
contrast, a user in a Cloudant® database can have read and/or write
permissions to the whole database or not have them at all. This limitation
removes one of the safeguards on the information in the database, and
requires more trust in the application programmers.
In this tutorial, you learn how to create a Cloudant proxy in Node.js.
Because you write that proxy’s code, you can put whatever security checks
you want into it.
What you’ll need to build your
application
- Basic knowledge of IBM Cloud, Node.js, Cloudant, and JavaScript
- An IBM Cloud Lite account (it’s free!)
Run the appGet the code
The
sample application
The sample application is a bank account system. There are three accounts:
alice, bill, and carol. Each account has a certain balance. The desired
behavior is to allow users to transfer money to one another, but not to
themselves. This application is implemented by using Cloudant and
OpenWhisk. To learn how to write such an application, see sections 1, 2,
4, and 5 of “Build a smart lock for a connected environment.” You can also
access the source code that is associated with that tutorial.
“Note that this type of policy, which explicitly forbids
some actions and implicitly allows everything else, is acceptable here
only because the application is so simple. In a production system, it
is best practice to have a policy that explicitly allows actions that
are permitted, and implicitly denies everything else.”
Step 1. Capture and relay HTTPS traffic
Because
some of the parameters are encoded in the path name, the proxy cannot run
as an OpenWhisk API application—therefore, I chose to write it as a
Cloud Foundry Node.js application. I describe the method of doing this in
my article “Add your own authorization proxy to a third-party app.”
There is one important difference between the use case that is described in that
article and the use case discussed in this article. Cloudant uses HTTP basic
authentication. This means that authentication information is part of the
header the client sends to the proxy. That information needs to be removed
from the header before sending the request to the server:
headers["authorization"] = null;
When you create the proxied request, add the server’s authentication
information. The added lines are lines 7–10.
// The options that go in the HTTP header var proxiedReqOpts = { host: cloudantCred.host, path: req.path + query, method: req.method, headers: headers, auth: { type: "basic", username: cloudantCred.username, password: cloudantCred.password, } };
The easiest way to do authorization is by using a separate middleware call.
Use a
function that gets HTTP requests, such as
app.all("*", function(req,
. The third
res, next) { … });
parameter (here called next
) is a function to call to return
the request for further processing.
// The authorization logic app.all("*", /* @callback */ function(req, res, next) { . . . next(); });
If the request is not authorized, set the response code to 401, and respond
with Unauthorized
.
if ( unauthorized ) { res.status(401).send('Unauthorized'); return ; // No need to continue this function }
Step 2. Get the relevant fields
The next step is to identify the fields in the request to make
authorization decisions.
Logging
It is useful to have a temporary log to be able to see the requests and how
they are parsed by the code. To do this, have a log string and display it
when asked:
var log = ""; app.get("/log", /* @callback */ function(req, res) { res.send(log); });
Anytime you want to add something to the log, you can add it as HTML. For
example, it is useful to have a line separating different requests:
log += "<hr />";
User and password
The user and password are available as the authorization parameter in the
HTTP header. The way that they are provided is a bit complicated, but this code retrieves
them:
if (req.headers.authorized !== null) { var origAuth = new Buffer(req.headers.authorization.replace("Basic ", ""), 'base64').toString('ascii'); var arr = origAuth.split(":"); user = arr[0]; password = arr[1]; }
Request fields
log += "<h2>User: " + user + "</h2>"; log += req.method + " " + req.path; if (req.body !== undefined) log += "<h4>Body:</h4>" + req.body;
Look at the log—these are the results of a single transaction:

As you can see, the application first reads the account for one user,
updates the balance for that user, and then repeats for the other user.
For GET requests, the user ID is in the path. For POST requests, it is in
the body, which is in JSON, as the _id
attribute.
The following code gets the ID and balance (if available) for both methods.
It uses the switch
construct, and looks for the information
either in the path or the body.
switch (req.method) { case "GET": id = req.path.replace(//.+//, ""); break; case "POST": var reqBody = JSON.parse(req.body); id = reqBody._id; balance = reqBody.balance; break; }
Context
You want to prevent users from increasing their own bank balance. However,
you can’t get that information from the GET
and
POST
requests. You need to know the existing balance before
the POST
changes it. There are two ways to find out the
balance:
- Submit a
GET
request from the proxy when you get a
POST
to modify an account balance. - Keep track of balances that you return to the application as responses
toGET
requests. There is no need to also look at
POST
requests because any request to update the balance
is going to be preceded by aGET
. (See “Document Versioning and
MVCC.”)
If there is only one instance of the proxy running at a time, then the
second method is much more efficient. To do it, create an empty hash table
as a global variable:
var knownBalance = {};
In the code that returns a response to the application, check whether there is a
balance that is being reported and if so update the hash table. The added lines
are lines 4–9.
var proxiedReq = http.request(proxiedReqOpts, function(proxiedRes) { proxiedRes.on("data", function(chunk) {retVal += chunk;}); proxiedRes.on("end", function() { var acctInfo = JSON.parse(retVal); // If we know about a user if (acctInfo._id !== undefined) { knownBalance[acctInfo._id] = acctInfo.balance; } res.send(retVal); }); proxiedRes.on("error", function(err) {res.send(JSON.stringify(err) + "<hr />" + retVal);}); });
Step 3. Write the authorization code
Armed with all this information, you can now actually write the
authorization code. In this case, you need to check whether the user is the
same as the account that is being changed. If so, check if the balance is
increased and if that is so, deny the transaction:
// The only case where we deny authorization if ((id === user) && (balance > knownBalance[id])) { res.status(401).send('Unauthorized'); return ; // No need to continue this function }
Note that this type of policy, which explicitly forbids some actions and
implicitly allows everything else, is acceptable here only because the
application is so simple. In a production system, it is best practice to
have a policy that explicitly allows actions that are permitted, and
implicitly denies everything else.
Potential pitfall: Transaction rollback
An important consideration when you use such a proxy is that the application
must be able to roll back forbidden operations. For example, in
the case of the sample application, a transfer of money from Bill to Alice
looks at the proxy (and the database beyond it) as two change
operations:
- Deduct the money from Bill’s balance
- Add the money to Alice’s balance
If the first operation is allowed but the second is denied (for example,
because Alice is also the user), the money from Bill’s account disappears.
This is undesirable behavior, which is difficult to prevent in the
proxy.
A properly written application is written with the assumption that database
operations can fail, and has code to deal with it. Here, the
modifyAccount
function has two callbacks: one called in the
case of success, the other in the case of failure.
// Modify a bank account var modifyAccount = (user, amount, cloudantUrl, callback, errCallback) => { var db = require("cloudant")(cloudantUrl).db.use("accounts"); db.get(user, (err, res) => { res.balance += amount; db.insert(res, (err, body) => { errMsg = JSON.stringify(err); if (err === null) callback(); else errCallback(); }); // end of db.insert }); // end of db.get };
The function that calls modifyAccount
uses the failure
callback of the inner call (the later one) to undo the outer call (the one
that happens first).
modifyAccount(params.fromUser, -params.amount, cloudantUrl, () => { modifyAccount(params.toUser, +params.amount, cloudantUrl, () => { returnHtml(success); }, () => { // Before reporting the error, undo the outer modifyAccount, which did succeed. modifyAccount(params.fromUser, +params.amount, cloudantUrl, () => {errorMessage(success);}, () => {errorMessage(success);}) // End of undo modifyAccount } // end of failure function for inner modifyAccount ); // end of inner modifyAccount call }, () => { errorMessage(success); } ); // end of outer modifyAccount call
If the application is not written with such a rollback function, providing
authorization in a proxy without causing an inconsistent state is more
difficult. You might be able to do it by caching all the changes that
relate to a specific transaction, but to do so you would have to figure
out a way to identify individual transactions. This might be possible, but
it depends on the application itself. There is no general method to do
it.
Conclusion
A Cloudant proxy cannot replace security in the application because it has
a lot less information and a much more restricted ability to communicate
with the user. However, as a component of “defense in depth,” it can be
another line of defense that an attacker would need to break through. This
proxy can also provide for a log of operations that is independent from
the application, in case an attacker breaks into the application
server.
Downloadable resources
Related topics
Credit: IBM