From the 102 session in the Couchbase LIVE New York mobile track, we continued iterating on the Couchbase Mobile sample application that was described in code from the “Couchbase Mobile 101: How to Build Your First Mobile App” session. 

In the Couchbase Mobile 102 session, we explored Sync Gateway in depth on its features and on “How to Add Secure Sync to your Mobile Applications”  by securing the Grocery Sync sample application which can be found on the Github repo for iOS and Android.  In this blog, we will walk through what was discussed on Sync Gateway, which is the component that ties together Couchbase Lite and Couchbase Server; you may reference the slides on the topic and code snippets that are outlined below. 

Key Mobile Data Security Concerns

One area that Sync Gateway helps resolve is data replication and this is regarding on how data is synchronized back and forth from the cloud to your mobile application on device.  The authentication of your users before replication can occur is another key area that Sync Gateway addresses upon which the data partitioning happens.  Once the replication is in place, data would need to be partitioned accordingly for your users to determine where specific data will be distributed.  And then there is data access control where given a user that has been authenticated, Sync Gateway may help with setting up the read and write permissions accordingly.  Let us explore each of these in more detail.

[1]  User Authentication

With the pluggable authentication model that Couchbase Mobile supports, there are a variety of ways to implement authentication where Sync Gateway allows for custom configurations to the security framework of the application architecture.  Sync Gateway supports three public providers with 'Basic Auth', 'Facebook', and 'Persona' authentication models.

Facebook Authentication:  Sync Gateway Configuration
The code snippet below illustrates how to configure Facebook for Sync Gateway.

In the configuration file, we defined that we want to use Facebook as the authentication provider.  Beyond this point, Sync Gateway will be doing the work to communicate to Facebook to authenticate the token for the clients.  
Custom Providers Authentication:  Communication Path   
 
To implement a custom provider where an user base exists in an LDAP server setup is where the mobile application points to the app or auth server first.  The auth server is then responsible for authenticating users where once it successfuly authenticates an user, the auth server would make an API call to Sync Gateway admin API in the second step to get a valid session for that user.  That session token then gets routed back to the auth server and then returned back to the mobile client.  The third and last step once the mobile application obtains the token is then to communicate to Sync Gateway directly using the session token to obtain all the data required for the user on the device.
 
[2]  Data Read and Write Access

A lot of the security questions are in the form of how we manage read and write access.  Sync Gateway allows for defining fine-grain security policies for read side policies at the document level and then define the write side policies down to the field level.  The general policy enforcement framework is based on a Javascript Sync function where it is flexible to define complex security rules to extend out the mobile application running Couchbase Lite. 

Sync Function:  Sync Gateway Configuration

The Sync Function is core to Sync Gateway on how to manage read-write access and is where the majority of the data access rules get defined.  So it is really the core of your security implementation where the Sync Function is a JavaScript function that gets executed whenever any JSON document gets written to Sync Gateway.

The function is defined in the Sync Gateway configuration file where the method signatures are taking the current revision of the document, doc, and the previous revision, oldDoc.  The body of the method is where the security rules are defined based on those two inputs.  This is where a simple basic Sync Function may first be defined and then slowly more advanced security rules may build out to cover all the cases.  With this approach, we can modify the sync function as new document types are defined or if there are an changes to the JSON schema.

Sync Function:  Write Permission

With the write-side security, we are able to determine if a particular document that is coming from a known user may be written to the backend store or not.  For the Sync Gateway, there are four key methods in the Sync Function to use for write-side permissions:  
  1. requireUser():  Takes as input a list of user id and validates whether the currently active users are part of that list
  2. requireRole():  Takes as input list of roles, where we can see if the requireUser has been granted that particular role. If they have not, then the document will get rejected.
  3. requireAccess():  Takes as input a list of channels where it is taking a list of current users and their list of channels to see if they have been granted a particular channel.  If the user is not part of the list then they will get rejected.
  4. throw():  The throw lets you do whatever inspection you want on the incoming document. You can have validation there and say for example if a document is type ‘item’ and if the type does not match then you can reject the document.  Similarly you can have validation that is based on a value being within a certain range.  Therefore the very granular field level type validation that can be implemented in the Sync Function, may be used with a throw() method on the documents that do not meet those criteria.  With throw() you can also provide error details on why it is thrown and the reason documents are rejected.

Sync Function:  Read Permission

With the read-side security, we are able to use “Channels” for data partitioning through the use of the 'channel()' primitive for documents while users get assigned to different channels using the ‘access()’ primitive.  The concept here is that every document gets associated with a set of one or more channels and that every user role has a set of channels that they can read.

A particular channel will become into existance as soon as the channel command is used to assign an user to a channel.  At that point, the user will be looking for documents that is tagged with the same channel name.  There exist two special channels with the star [*] and exclamation [!] parameters.  

  1. Star[*]:  The * Star channel is the channel where every document is automatically added to that channel.  An example is where if a guest user is granted the * star channel, they will have open ended access to all the documents.  A clear use-case for this channel attribute would be similar to admin privileges where any user that that has the * star in their channel will be able to see everything in the system. 
  2. Exclamation[!]:  The second special channel is the ! exclamation attribute that is used to describe the public channel that is focused from the document's perspective where if you add a document to the public channel, then every user will be able to see that particular document.  A clear use-case for this channel attribute would be similar to having a document for public announcements or broadcasting a message to all users.

Sync Function:  By Example

In the following example, we will explore using the features described above on how to secure the Grocery Sync Application from the Couchbase Mobile 101 session where it exists as a completely unsecured application with no privacy.  Iterating with Sync Gateway, users will end up seeing only their own items as oppose to seeing the same list where all the available items that are added.   

The starting configuration file for Sync Gateway is where nothing is switched on and is the default setting that comes with the Grocery Sync application.  We also enable the guest user so that the user has access to all the channels which means we will see all documents and new items that are added.  The first thing to adding security is to have users to authenticate which is what we will implement below.

The idea is to identify individual users that are part of the application and use that as a security measure within the app.  The update to the config file is creating users Alice and Bob.  We remove the Guest account and use Basic Authentication by pre-defining two users where initially the users have access to all channels.  Now with users created, we want to create a custom sync function to allow for defining the read and write security logic for the users in the app.

We now add a placeholder Sync Function and this is where the we will focus and continue to iterate on the logic to provide access control for the users on the data in the system.  Next we will further secure the channels by providing specific channel names. 

Now we will give users access to their own items channel in the sync-gateway config by removing the [“*”] star for the admin-channel and add a new private channel that is specific to the user such as in the case here, “items-alice.”  Previously with the [“*”] channel, it allowed users to see all the documents in the system and we do not want that as a security measure.  We do the same for ‘Bob' as well by setting the 'admin_channels' to be specific to “items-bob.”  By doing so, we will have items only accessible to their owners so we need to link the owner field in a grocery item document to the owner personal items channel accordingly.  Next, we will further modify the sync function for that to occur.

When a document is being written into Sync Gateway, we assign it to a channel where the channel name is of the form of “items”-prefixed with the value of the owner property from within the owner document that is obtain by “doc.owner.”  The previous configuration is when the document property is set to ‘bob’ and the ‘items’ would then be assigned to the ‘items-bob’ channel.  The user, “bob” has access to the “items-bob” channel and basically that is the channel that Bob has been given access to in his user record.  Now any item that has owner 'bob' as a property will be seen through that channel by Bob himself but it will not be seen by Alice cause it will not be in the ‘items-alice’ channel.  What occurred in the configuration is that we assigned a document to an appropriate channel and also programmatically gave access to the owner through their owner “items-” channel. 

So now users can only see/read their own items and effectively their own grocery lists.  But they can still write to each other’s list cause Bob could upload an item and set the owner property to be “Alice” where in effect, Bob may easily infratrate Alice’s Grocery list.  The next step in adding security is to make sure that when a document comes in, we would have the owner-property match with the current authenticated user.

The updated configuration now contains the 'requireUser' function in the beginning of the sync function with an input parameter being the owner property that is obtained through 'doc.owner' from within the document.  Basically the current authenticated user that is trying to upload that document must have an user ID that match the owner property in the grocery item document that they are pushing up.  And if they do not match then it will throw an error in it for the new grocery item as it will not accept.  Now users have their own grocery list and it is private where no one can write to them.
It would be awesome where users can add friends to add items to a particular list.  What we will update next in the configuration is the ability to add a new 'document' type with a 'friend’s doc type' and this document will be used to grant friends access to the given users' grocery list.

Given that there are now a 'type' key in the two documents which can be differentiated by 'items' or 'friends' document types, we can now add a switch in the sync function to allow us to process the different documents in different ways within the Sync Function
We do a check for the document type first to see that if it is a 'friends' type and then first see if the document owner property matches the current authenticated user ID through the 'requireUser()' method and if it does not then we will reject it since only the owner can invite friends to update their grocery list.  If the owner property does match the current authenticated user, then we will give access to all of the friends through the 'items-doc.owner' channel.
So for example, when bob puts up his friends document to invite Alice, we basically give Alice access to items-bob channel.
Then we are going to map the document that is a 'friends' type to a channel called 'private-doc.owner' which in this case will be 'private-bob' and notice this time, this is a dynamically created channel.  It does not create this in the user record admin channels and you see that these channels are created on the fly when the user first sends out an invite.  This basically means that no one else will see the list of friends that has been invited by the user as that is private only to the user themselves.  And then we are going to give the 'doc.owner' or the current authenticated user(s), access to that new private channel that has been created for them. 
The 'else' clause is where if this is not a friend’s document, then this will be a grocery sync item document and again we validate that the owner property matches the current authenticated user ID.  As before, we add the items to the 'items-' channel for the item owner property and we give that user dynamic access to that channel.
While we updated the Sync Function with the switch logic above, we can actually add an additional test to reject any unknown document types so if it is not a 'friends' or 'item' document’ type, then we will reject the document by calling the 'throw()' method. 

You can see we added another 'else' clause to capture all the other document types and we will just simply throw an “invalid document type” message which will then be sent back to the client.  This is a way to stop people from putting in things we do not want in our database. 
So now that we have invited friends, we want to actually give friends the ability to write new items to the owner’s list.  What we have to do is change the previous 'requireUser()' method to 'requireAccess()' so not only can the owner write items to the items channel but anyone that has been given access to that particular channel may also add/write new items.  This is where friends are only given this access to an user’s item channel via an invite from a friend’s document.

Instead of the 'requireUser()' method, we will replace it with the 'requireAccess(“items-“+doc.owner)' method where now basically if you want to write a new item to a list, you have to have access to the items channel for the particular user that you want to add the item for.   Some additional features for the app is that if a friend writes a new item to our list, we want to make sure they do not have the ability to check the item off.  So what we want is to have them only add new items that are unchecked and then only the owner of the grocery list can then check them off accordingly.  We will do an additional check where a friend writes a document, that the document does not have its property set to true and thus testing for doc.checked property’s boolean value.

First we look to see if ‘oldDoc’ is null which would mean that it is a newly created item.  So we will validate the ‘check’ property in the document to see if it is true and if it is true then we will throw an error where the 'new items cannot be checked’ when they are created.  We only want owners to check or uncheck items when they have been created and we will validate this by making sure that there is a (doc and oldDoc) which means there is an update to the item and then we will see if the ‘check’ property has been changed between the previous version of the item and the new version.  We can do a simple check to make sure that the ‘oldDoc’ is not NULL and then requireUser(owner) but we also want friends to be able to correct typos on the item’s text as well, even if they can not check off an item, friends may want to change the item title or label.  Next, we will do field level validation to see if the previous oldDoc exists or not.  So we will compare ‘doc.Checked’ to ‘oldDoc.checked’ to see if its changed.  Then if it has changed, we will do a 'requireUser(owner)' on the owner of the document since they are the only ones that can change the check box.

Looking at the 'else clause' there is additional code to see if the ‘check' value has changed between the old previous version of the item and the new one that is being pushed.  If it has changed, then we require that the current authenticated user is the doc owner.
So that only an owner can change the check box.  But other friends of the owner can change things like the title of the item.
We got one last problem that we want to look at and sort out.  We want to make sure that no one can change the ‘owner' on an item once it has been created.  That means friends can make modifications to the labels but not change the check box on an item or change the actual owner of an item.  Let us see how we can address that problem in the final configuration.

So we will do an additional check and if this is an update where 'oldDoc != nil' and the owner of the new version of the document does not match the owner of the previous version (doc.owner != oldDoc.owner) then we will throw an error.  Now, you can see we are doing an additional test so that if the owner has changed between the previous version and current version, we will then throw an error basically back to the client as they cannot change the owner of the document.
 

[3]  Data Transport on the Wire

Now, aside from the authentication and data read/write for user access concerns with security, the next topic is to ensure your data is secured going on the wire to a remote endpoint.  Sync Gateway supports SSL and TLS on the transport.  It is straight forward to configure within the Sync Gateway config file to enable SSL on Sync Gateway for your mobile app to have additional data security.

[4]  Data Storage

For data storage on the client, you are really doing file system encryption on the device.  There is good information on what you need to do to encrypt the local Couchbase Lite database on the mobile developer portal.  We are talking about a secure cloud environment and configuring that for file system encryption on the client.

Summary

Sync Gateway is really the piece that glues together Couchbase Lite, the framework that lives on the embedded device. You have your Couchbase Server running in the cloud and Sync Gateway ties those together.  Sync Gateway has a handful of key functions that it provides to turn an application that is running on your local device, which is a standalone disconnected application into a fully feature multi user synchronizing experience.  What is shown above is how we can serialize things in the database by iterating on the Sync Function configuration file.

Next we will go into the Couchbase Lite HTTP listener component class is the Couchbase Mobile 103 session on how to enable the Peer-to-Peer feature where you can create unique social in-app experiences by “Building a Peer-to-Peer App with Couchbase Mobile.”

 

Posted by William Hoang, Mobile Developer Advocate, Couchbase

William was a Developer Advocate on the Mobile Engineering/Developer Experience team at Couchbase. His love for coffee and code has transcended him into the world of mobile while appreciating the offline in-person experiences. Prior, William worked on the Developer Relations team over at Twitter, BlackBerry, and Microsoft while also having been a Software Embedded GPS engineer at Research In Motion. William graduated from McGill University in Electrical Software Engineering

Leave a reply