Contents


Authorize with a Cloudant proxy

Safeguard your application with another line of defense

Comments

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, res, next) { … });. The third 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:

log results
log results

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 to GET requests. There is no need to also look at POST requests because any request to update the balance is going to be preceded by a GET. (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:

  1. Deduct the money from Bill's balance
  2. 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


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Security
ArticleID=1054965
ArticleTitle=Authorize with a Cloudant proxy
publish-date=12062017