Use a Middle Tier with the REST API

by Dave Cassel

MarkLogic is flexible about how it's used in a stack. You can go two-tier, with a browser (or other client) talking directly to MarkLogic or you can use three tiers, with a middle tier controlling access to MarkLogic. The MarkLogic REST API was built with a three-tier architecture in mind, where that middle tier applies business logic to ensure that only proper requests get through to the REST API.

The REST API provides a set of commonly-needed capabilities out of the box: search, document management, alerting, semantics, and transaction management, just to name a few. Using these capabilities is a solid architecture decision, but they should be used in their proper context. If you're using the REST API, you should use a middle tier that doesn't simply proxy requests through. This isn't just about separation of concerns; it's about security. The problem comes down to controlling what changes a user can make.

Let's consider an example. Suppose you have an application where you allow logged-in users to vote on content. Maybe your documents look like this:

Architecturally, consider a two-tier application where the end-user's browser talks directly to MarkLogic's REST API. (If we use a three-tier application where the middle tier passes all requests through to the REST API, then the situation is functionally the same.) There will be a role for the guest user (who can read, but can't update anything), as well as a role for users who log in. What permissions and privileges should we grant to the role that logged-in users get?

From a security point of view, we need to remember that the user interface isn't the only way to interact with the server. A malicious user can bypass the UI and send HTTP messages directly to the server, so we have to account for that happening. In the case of a two-tier, REST-API-based application, that means a user can interact directly with the REST API.

Okay, let's come back to the voting feature. In the UI, there will be an upvote and a downvote button, which are only active when the current user has been authenticated. When one of those buttons gets clicked, the browser will send an HTTP message to the server, perhaps like this replace-insert:

That is, insert user dcassel's upvote if he doesn't already have one on this content, otherwise replace the old vote. (In a real application, we might skip the replace if the upvote already exists, and we'd need to delete a downvote by this user if there is one. We could handle this through a client-driven multi-statement transaction, or through a replace-library call.)

What if a non-authenticated user tries to send the same message using someone else's account? Document-level security takes care of us here -- the role for authenticated users will have update permissions on the content documents (perhaps inherited from rest-writer); the default user's role will not (perhaps having only rest-reader).

Suppose that an authenticated user wants to get a lot of upvotes for content he or she likes. What prevents that user from sending messages like the above PATCH with a variety of usernames, which may or may not correspond to real users?

Unfortunately, given that the malicious user has direct access to the REST API, nothing prevents this. Document-level security can't block this while allowing proper votes. (Element-level security doesn't either.) The problem is that this isn't so much an access-control problem as it is a business-rule problem: a user may only record one vote, and only using his or her account. This easily solved with either of two approaches, both of which prevent direct access to the REST API.

A common approach is to use a middle tier that applies business rules. You can use Java, Node.js, Python, or any other language you like. The key is that the middle tier receives the HTTP requests from the client, validates that they are proper, and then passes them to MarkLogic if appropriate. In this case, your middle tier might offer an endpoint at /api/votes/up/{doc-uri}. Notice that we don't even give the user the opportunity to provide the username -- the middle tier will already have it in the session if the user has authenticated. The middle tier checks whether the request comes from an authenticated user, then constructs the necessary PATCH request for MarkLogic.

The other approach is a two-tier architecture without the REST API. MarkLogic provides an application server, so you can host your entire application there. If you take this approach, you'll provide main modules (written in XQuery or Server-side JavaScript) that apply the business rules and make the database updates. For instance, you might implement /api/vote.xqy, taking parameters for up-or-down and the URI of the document to be voted on.

Are there cases where using the REST API in a two-tier application is okay? I can think of two:

  • a read-only application: if your users are not able to update the content in any way, AND you're okay with them sending read requests for any document for which they have permissions. Note that this may include configuration or user profile data.
  • a proof-of-concept application: if you're building a POC and security isn't something you're concerned about, this approach can allow for rapid prototyping. The (now deprecated) App Builder and the slush-marklogic-node project generator both use this approach. The key thing is to know that there may be a lot of work to move this POC to production.

The REST API provides out-of-the-box capabilities that are well-tested and written by engineers who know how to maximize MarkLogic's performance. It's a great tool, but be sure to use it in the proper context.