Push/Pull replication with authentication

How do I configure replication to push/pull only the current user’s data? Do I only need to se up authentication or do I need to set up a Filter and configure the stored documents to have an “owner” property to filter against? Another alternative I’m considering is setting up the sync gateway sync function to only give access based on a uniquely identifiable document property like “owner” or “username”.

The scenario I am trying to resolve is:

  • I have a mobile app in which each unique user creates their own data
  • All user’s data are pushed to a single couchbase server
  • I want the pull replication to only pull down data that belongs to the user of the mobile app and disregard all other users.

Does authentication even matter in this case? Other than prevent users from accessing the server?

Use channels to do this. The sync function puts documents into channels based on their properties, and user accounts have access to the relevant channels. A common thing is to define a channel per user (maybe named something like user-jens) and then put an owner property into the document. You can get fancier by defining roles, and/or by defining channels for higher-level purposes and then giving users access to those channels as needed.

In order to guarantee that the owner property is unique to each user, I would need to check if the owner property already exists or defined by another user and request the user to pick a different owner name…this would require some custom server code to go through the Admin REST Api to check if the channel already exists for another user?

You make the channel name unique by basing it on the user name, which is of course unique. That’s what I meant by the example above, where the channel name is "user-" + userName (in pseudocode.)

Hi,

I’d like to do the same - my setup is very simple: Each authenticated user (facebook) has only access to their own channel.

I have a sync_gateway & couchbase db (both latest community versions) running on an EC2 instance. I’ve created a sync_gateway config file which I’m pretty sure is wrong as I can push but not pull.

I have disabled GUEST access so only authenticated users can push & pull (using facebook auth)

I’m struggling to find out how to get hold of the facebook userid or username (anything user-unique) so that I can do something like:

function(doc) {
var userChannel = “user-” + userId/Name
channel(userChannel);
access(userId/Name, userChannel);
}

Firstly, am I approaching this the right way? I’m new to couchbase.

Provided I’m correct, how do I get the authenticated user id in the sync function? Or do I have to explicitly set the channel on the client (iOS/Android/whatever) before I push the document to the server?

Thank you

That’s intentionally not possible. (There are cases where the doc has to be run through the sync function by internal maintenance processes, not by the user who modified the document; in that case a value like “currentUserID” would end up being null or otherwise non-useful.)

What you do instead is have the client app put the user’s ID into a document property, something like owner. Then the sync function should call requireUser(doc.owner) to validate that the user changing the document is the document’s owner. (This works even during maintenance, because in that mode requireUser() is a no-op.)

Now you have the user’s ID in the doc.owner property and can use it as in your snippet above, to derive a channel name.

Thank you for the quick reply Jens

To clarify, is this what I need…?
…where requireUser protects against writes from other users and access enables pull-ing data?

function(doc) {
requireUser(doc.owner);
channel(‘user_’ + doc.owner);
access(doc.owner, ‘user_’ + doc.owner);
}

Thank you

Yes, although you could instead assign the channel access manually, rather than via the access call — i.e. when creating a user account, add the matching channel to its list of admin_channels.

Assigning it manually will be slightly more efficient, but there’s probably not a noticeable difference unless you have a large number of documents, so don’t worry about it if you’re just getting started.

@jens

It looks like channel names are restricted to a very small set of special characters: Valid channel names consist of text letters [A–Z, a–z], digits [0–9], and a few special characters [= + / . , _ @]

I wouldn’t be able to use a user’s email address then? Since email address allows many more special characters such as ! # $ % & ? ^ { | }.

Alternatively, if I were to prompt the user to enter a unique username so that I can use the username as the channel name, I would need to do additional client side validation to prevent users from entering invalid characters?

Is there anyway around this restriction?

Sync Gateway user names are already (intentionally) limited to the same character set as channel names. So it’s always safe to use a user name as a channel name.

If the user enters the user name when registering you can do local validation if you want, but even if you don’t, your app server will get an error trying to create the account if the name is invalid, and will presumably return that error back to the device.

How does Facebook authentication work then if the sync gateway implicitly creates a user account where the username is the same as the authenticated email address? Would you run into an app server error as well if the authenticated e-mail address contains invalid characters? In which case, you would never be able to register/authenticate with a Facebook account that has special character e-mail address.

That’s a good point. (Actually I believe we are changing the Facebook integration to derive the username from the Facebook account UUID instead of the email address, because the email can change. I don’t know if this change has happened yet.)

Looks like the SG issue covering the character set limitations is #656; you’re welcome to subscribe and/or comment.

I finally got around with implementing the facebook authentication and noticed that when I do a push/pull replication using the FacebookAuthenticator with non-existing user and Sync Gateway implicitly creates the user from the access token and email info, the username field is actually the Facebook User ID and not the user’s email address. Is this expected? The current documentation says that the email address would be the username.

Also, my replication fails with a 401 - Unauthorized error/login required even though a user and a session got created in the database. Am I missing something in the configuration? I get the same issue when going through the REST API /_dbname/_facebook

Here’s the output of the SG console:

HTTP:  #002: GET /sync_gateway/_session
HTTP:  #003: POST /sync_gateway/_facebook
go-couchbase: call to ViewCustom("sync_gateway", "access") in github.com/couchbase/sync_gateway/db.(*DatabaseContext).ComputeChann
elsForPrincipal took 473ms
HTTP:  #004: GET /sync_gateway/_local/82b5437a4586262d94a5866682aec15ad0f63d23
HTTP: #004:     --> 401 Login required  (1.0 ms)

My Facebook App settings has permission allowed for “public_profile” and “email” and is set to available to the public

That’s the change I referred to above. Sounds like it’s already happened.

Also, my replication fails with a 401 - Unauthorized error/login required even though a user and a session got created in the database

Sounds like the replicator isn’t sending the session cookie. What platform is this, and how did you create the session? On iOS I think you’ll need to explicitly add the session cookie to the CBLReplication object.

I am developing on Xamarin for iOS platform. Does that mean I need to do a separate login via the REST API, get the session cookie from the response, and then set the cookie on the replication object? Currently, I only create a Facebook authenticator object and set it on the replication object (and assuming that login and user creation is all handled behind the scene by the replicator).

You’re right, you should just need to set the Facebook authenticator.

I don’t work on the .NET version, but it looks like the replication should have worked — in the SG transcript, the POST to /_facebook should have returned a session cookie that the next request should have sent back. You may want to file an issue on Github.

I’ve submitted a bug: https://github.com/couchbase/couchbase-lite-net/issues/489

Replication seems to work correctly if I use basic authentication with the standard username/password.

@couchdb_user
@jens can you help me out.
Have you got any solution on this… for me also same problem with guest it is working fine but replication not working with session…

Session am creating before and trying to pass with replicate my request as following.

Post
var cookie = result.data.cookie_name + “=” + result.data.session_id;
var source = {
“url”: “http://syngateway.com:4984/db”,
“Cookie”: “SyncGatewaySession=ad33bcf2dafdbbe99bd3e416e9fa84fd8e0d9b82”
};
couchlite.messages.replicate(source, tableName, {
replication_id: ‘inbox-intial-sync’,
continuous: true
}).then(function(result) {
console.log(result);
$state.go(‘main.inbox’);
}, function(error) {
console.error("from live ERROR -> " + JSON.stringify(error));
});