How to authenticate with user/session info when creating a replicator with couchbase-lite-core

Hi,

So far I have been tinkering with couchbase-lite-core API and sync gateway using a guest account.

Now I want to use custom authentication to make my API create users and sessions so that a user can enter it’s own api defined bucket.

While reading about custom authentication (https://docs.couchbase.com/sync-gateway/2.1/authentication.html#custom-authentication) all makes sense but while trying to make the whole flow work I have stumbled on a question:

How do I pass authentication information from the client side on couchbase-lite-core?

I see defines related with this topics such as kC4AuthTypeBasic, kC4AuthTypeSession but I can’t seem to find a way of passing them.

So, if I dynamicaly create a user, a database and a session for that user on a database… how can I authenticate from the client side with that dynamically created user/session pair?

Thanks in advance!

Best regards,

Nuno

Those options are passed in via the optionsFleece parameter on C4Replicator. You can see a list of what keys you can pass in a little further down

@borrrden thanks for your reply. I would not get there without your help.

So, if I have a session token for a certain user, is it enough to pass the the kC4AuthTypeSession key with the token as value?

I don’t know if that is clear to me but it seems that I need to pass the key kC4ReplicatorOptionAuthentication with another dict as value, with the following keys:

kC4ReplicatorAuthType → kC4AuthTypeSession
kC4ReplicatorAuthToken → token

Is this correct? Am I missing anything?

Thanks!

Regards,

Nuno

Actually I have to take that back as not the entire story. You are probably going to be disappointed by this but LiteCore does not actually handle this stuff at all. It’s been a while since I’ve been through that area and while it is true that the information is passed through in that fashion, you need to “bring your own connection” so to speak via C4SocketFactory. The replicator will simply hold on to this information for you and you will get it back inside of the open callback.

Couchbase Lite (i.e. not core) brings a web socket implementation to the table for this purpose and hooks it up (as well as creating an interface for you to hook up your own for P2P purposes). For C/C++ you can have a look at the CivetWebSocket class if you want to get some hints. This is an implementation which supports web socket plain but not web socket secure (wss) for transport. So really, for the format, you are free to store the information however you like. We have some predefined structure so that we can have a similar implementation between Couchbase Lite platforms but it is arbitrary and not required to be that way except for the sake of consistency.

@borrrden I’m getting used to not being easy! :slight_smile:

Luckily I have already a websocket wrapper for SSL support.

So, when the socket is initialised, the parameters are passed to the header ? The header name is defined by the name of the constants?

I’m going to dedicate this morning to this. I will definitely stumble in something. I will get back with feedback!

Thanks for your help!

Regards,

Nuno

A C4Socket object is like a shared container between LiteCore and you on which you store your transport in accordance with a given connection between a specific local and remote endpoint. The socket factory (that you define) is responsible for adding in and participating on your side. One of the callbacks to the socket factory is an open function which will pass you the following information:

  1. The C4Socket object that is going to be used going forward
  2. The address of the remote endpoint being connected to
  3. The options that you initially passed in (and some defaults if you didn’t pass things in for a given option)
  4. A socket factory context object (defined on the factory and you can use this however you wish to pass domain specific information throughout the lifetime of a socket)

Your job in this method is to create your connection and open it. This connection object can then be stored in the one field of the C4Socket struct (nativeHandle). Going forward the other methods will pass you the same C4Socket which will have your connection attached to it (so e.g. in the write method, you need to write the data passed to you through your connection which you will find attached to the C4Socket)

If that explains the concept well enough you can find a lot of notes in the c4Socket.h header file containing more details.

@borrrden thanks for your reply.

I think I have managed to pass the auth parameters to the replicator initialization but I must be missing something as the sync gateway says login required;

First I have used the admin REST api to create a user on the database:

http://localhost:4985/db/_user/" -H "accept: application/json" -H "Content-Type: application/json"
-d "{ \"name\": \"user\", \"password\": \"12345\", \"admin_channels\": [ \"string\" ], \"admin_roles\": [ \"string\" ], \"email\": \"user@acme.com\", \"disabled\": true}"

Then I have passed user and the password to the replicator and started the program.

21:34:06.132288| [Sync]: {Repl#4} Push=continuous, Pull=continuous, Options={{auth:{password:"****", type:"Basic", username:"user"}}}
21:34:06.597746| [Sync]: {Repl#4} No local checkpoint ‘cp-vlmCFJwFmbEAAIw3B3XecX1Kp8E=’

But nothing happens behind this point and sync-gateway log says:

sync-gateway_1 | 2019-04-23T20:34:06.898Z [INF] HTTP: #004: → 401 Login required (3.6 ms)

At this point I believe I’m not actually passing the whole info to it or something else is wrong.

Any ideas?

Thanks!

Regards,

Nuno

Your user request looks odd. If you send disabled as true then you won’t be able to use that user.

Also to reiterate something: LiteCore will only store your information, you are responsible for extracting it and sending it over your transport (in the websocket case, the auth should be in the HTTP headers of the web socket upgrade request)

@borrrden yes, I have stumbled upon that detail while digging.

I’m still struggling to understand how databases and users work. For instance, I have a database configured with a user name and password. Through the server dashboard I have created a bucket. But if I use the sync-gateway admin api to create a database, I can’t see it through the server dashboard. Neither I can see created users.

Also, the API explorer for the databases call doesn’t seem to be working correctly. It doesn’t seem to fill the curl properly so I need to write the escaped JSON by hand which takes me much more time to explore the API.

C you please tell me exactly which headers (or point me a document where that is specified) should I use in the authentication process of the websocket open?

I’m using the QCBLWebSocket class made available by this project as a wrapper to couchbase-lite-core in Qt. I’m using Qt myself too.

While I’m passing the auth info to the replicator initialisation the fact is that it doesn’t seem to be used during websocket open.

I have a question. What’s the name of the auth headers that sync gateway expects to receive in the other side?

void QCBLWebSocket::startSocket()
{
    QUrl url(QString("%0://%1:%2%3").arg(scheme,host).arg(port).arg(path));

    QNetworkRequest r(url);

    for (Dict::iterator header(options.get(kC4ReplicatorOptionAuthentication).asDict()); header; ++header)
    {
         QSlice key(header.key().asString());
         QSlice value(header.value().asString());

         r.setRawHeader(QSlice::qslToQByteArray(key),QSlice::qslToQByteArray(value));
    }

    QSlice cookies(options.get(kC4ReplicatorOptionCookies).asString());

    if (!cookies.isEmpty())
         r.setRawHeader("Cookie",QSlice::qslToQByteArray(cookies));

    QSlice protocols(options.get(kC4SocketOptionWSProtocols).asString());

    if (!protocols.isEmpty())
        r.setRawHeader("Sec-WebSocket-Protocol",QSlice::qslToQByteArray(protocols));

    open(r);
}

This are the keys there are being filled upon replicator construction. Inside the socket they must be put into the Raw Header.

#define kC4ReplicatorOptionAuthentication "auth"
#define kC4AuthTypeBasic “Basic”
#define kC4ReplicatorAuthType "type"
#define kC4ReplicatorAuthUserName "username"
#define kC4ReplicatorAuthPassword "password"

How should I place this info into the Raw Headers? Because I have already tried the way above and the result is exactly the same, sync-gateway is logging that the request requires authentication.

Thanks in advance!

Best regards,

Nuno

Sync Gateway (and other web socket servers) expects a standard HTTP Authorization header. Once you receive the info from LiteCore and are ready to open your connection everything after that is not in the LiteCore domain and is governed by web socket specifications (once of which is the negotiation of a web socket connection via an initial HTTP request).

Here are some points that I will respond to:

Unclear what you mean here. Do you mean a Couchbase Lite database (in which case what does it mean to “configure with a username and password”), or do you mean Sync Gateway configuration?

What are you expecting to see? Part of the Sync Gateway database configuration is the specification of which existing server bucket to use. It will not create any of its own (at least I don’t think it will). As far as users, they should show up in whatever bucket that you have specified in the Sync Gateway configuration, but the format that we store them in is not immediately obvious. The way Sync Gateway stores its own data in the bucket should be considered opaque for your purposes.

Ok, it should be straightforward! Thanks!

However… I’m still very confused with couchbase data model.

I believed that sync-gateway acted over couchbase-server. What’s the point of having an API on sync gateway to create users and create databases if then they are not actually created in database engine?

My goal is to have a dynamic system in which I create databases on demand for new users. I don’t want to pollute this thread with data model questions. I have another thread with a related question. Maybe you could help me understand -> Try to use 5 or less buckets in Couchbase. Never more than 10

Thanks!

Nuno

Users are created in whatever backend you choose for Sync Gateway (walrus or server) you just might not notice them. As far as databases, part of creating them is specifying which buckets are going to contain the data for the Sync Gateway database (which is more like a concept than an actual thing. Think of it as a “virtual” database or a proxy).

This sounds like a job for channels, not separate databases.

@borrrden I have been able to go through with Basic authentication. Adding the authorization header to the open request of the web-socket did the job. Thanks!

Regarding the database design, please let me provide more information. I have several apps and several users. I want to create a database per user_id and app_id so that when a user log with its account on a certain app, the content will be synchronised across devices. That’s why I choose couchbase in first place. I have searched extensively for a solution that would cover all platforms reliably.

My apps are not data extensive. I just want a reliable preset cloud sync system. Right now each preset is only a JSON but tomorrow it can also have binaries files associated with.

When looking around to couchbase documentation, and I have been looking around a lot in the last three months, I thought that I could do something like this: I have one single database and a bucket for each user_id:app_id combination.

When a user signs up our api, the user is also created through sync-gateway in the database side. If the user is logging in from a specific app, the api wil search if there is a bucket with the name user_id:app_id and if not it will be created and that user added to it.

I don’t want a common database that has all the users content in one place. I want to be able to synchronise only that user_id:app_id combination of data to avoid wasting bandwidth with data that is not relevant.

Let’s suppose I have 100K users and 10 apps. In the end I will have around 1M buckets. Right now I don’t even scratch the bottom of those numbers, but do you think the way I’m thinking about couchbase data design is correct?

For a newcomer I find documentation very sparse and incomplete. When I go through some documents that talk about server and sync-gateway I always get to the end of them without feeling confidence of the acquired knowledge.

However, your help and @jens help has been priceless! :slight_smile:

Thank you so much for your availability.

Best regards,

Nuno

This is the precise reason that channels exist FYI (documentation on data routing is relevant here)

Thanks @borrrden! There is so much to look at. I totally missed that. Thanks for the tip!

One more question related with authentication…

When you use a Basic auth with username and password you set the authorization header to Basic XYZ where XYZ is the base64 encoding of username:password.

What about authentication by session?

I have tried to change Basic to Session and XYZ to session id but not luck. I also tried to Basic XYZ where XYZ was cookiname:session id but no luck.

I prefer to authenticate with session rather than username/password because I don’t want to save and update passwords. I prefer to create a session when a user needs.

Where can I learn more about the authentication process details using session id?

Thanks!

Regards,

Nuno