User authentication plugin for PouchDB work with Sync Gateway

I want to use the following plugin to Authenticate Users on PouchDB/Sync Gateway combination:

Will this plugin work to authenticate on Sync Gateway?

I’ve tried using it to figure out the difference between using this plugin with CouchDB and with Sync Gateway. It won’t work because the API to login with SG is not the same as the one this plugin expects.

  • Sign Up won’t work because Sync Gateway users must be created via the Admin port (4985).
  • Log In won’t work because the plugin makes the session request to http://localhost:4984/_session but SG expects a database in the path http://localhost:4984/{db}/_session.

You can follow this blog post to get the code to set up an app server (reverse proxy) to expose the admin rest api endpoint to client apps to register users. The blog post shows how to sign up the users in an Android app but the same concept applies to web clients (it’s the same HTTP request).

James

1 Like

Thank you so much James. After reading the blog following is my understanding, is it correct?
1)- If we want to use custom authentication, user can only be created using the admin REST api which is not public and can only be called from server
2)- We will have to create a proxy server on port e.g. 8000 which redirects all requests to sync gate way on normal port:4984 except for the /signup request.
3)- /signup request makes a post request to syncgateway admin REST api on port:4985. The POST Api call will be /dbname/_user
4)-We will be using the 8000 port proxy url for all pouchDb references for web, Android and iOS
5)- Our architecture will be that web browser pouchdb client, Android client and iOS client all connect to the proxy server (node.js). The node.js/ proxy server forwards all requests to the sync gate way and the sync gate way talks to the couchbase except for signup.

1 Like

Yes that’s correct.

Let me know if you have questions about the implementation or if things are not working as expected.

James

1 Like

There’s one thing I don’t get from the code in the blog post:

How can the proxy work for login if it uses the admin port?

I tried it and it worked for me, but why?

Shouldn’t I need to create a '/login' URL with POST to redirect the request to the admin port? In the code from the blog, it redirects all requests to the public port, 4894. Both the curl version and the Java version do the same. Why is it working?

Good question, because the session endpoint to login is actually also available on the public facing port. I think the docs don’t make this clear enough (actually they don’t mention it at all). We’re fixing that. Sorry for the confusion.

James

Thanks for the clarification.

So the _session endpoint is available both in the public and admin ports? Is there any difference in behaviour between the two?

The _user and _role endpoints are still only available in the admin port, correct?

Yes, it’s the same functionality on both ports.

Yes

Incidentally, the docs have a short note on the public _session endpoint (it’s under REST API, so I assume it’s about the public port):

http://developer.couchbase.com/documentation/mobile/1.1.0/develop/references/sync-gateway/rest-api/server/index.html

Yes thanks - were you looking in the Authentication section? http://developer.couchbase.com/documentation/mobile/1.1.0/develop/references/sync-gateway/rest-api/authentication/index.html I think that’s the right place to put it.

Yes, I guess that would be better.

Also, make it more explicit that it works on the public port =]

Actually, those endpoints do the same thing (create a session for a user) but with different responses.
On the public port, the sync gateway cookie is returned as a header

Set-Cookie: SyncGatewaySession=7da94f20380a41d09ab6e382a0c28a12a05861e0; Path=/sync_gateway/; Expires=Thu, 24 Sep 2015 16:38:18 UTC

And on the admin port, it’s returned in the message body:

{
  "session_id":"6e3251294a734b99b821e9031fc446bd2dc679f5",
  "expires":"2015-09-24T17:38:09.23133207+01:00",
  "cookie_name":"SyncGatewaySession"
}

James, I have being working with the reverse proxy as you suggested. The issue is that pouchdb is not sending any request to the reverse proxy. When I use the sync gateway URL everything is working fine. The only code I change on the client is the reverse proxy URL and it stops working because no messages are being received on the reverse proxy from the pouchdb. It seems pouchdb is using some other way to communicate with the sync gateway e.g. a socket. My be I am doing something wrong? I have been trying different code combinations this is one of them:

var express = require(‘express’),
session = require(‘express-session’),
cookieParser = require(‘cookie-parser’),
bodyParser = require(‘body-parser’),
hbs = require(‘hbs’),
request = require(‘request’).defaults({json: true}),
httpProxy = require(‘http-proxy’);

app = express();

// Allow origins
app.use(function(req, res, next) {
res.header(‘Access-Control-Allow-Origin’, “http://localhost:8081”);
res.header(‘Access-Control-Allow-Origin’, “http://localhost:3984”);
res.header(‘Access-Control-Allow-Origin’, “http://localhost:3985”);
res.header(‘Access-Control-Allow-Origin’, “http://91.123.200.21:8091”);
res.header(‘Access-Control-Allow-Origin’, “http://91.123.200.21:8093”);
res.header(‘Access-Control-Allow-Credentials’, “true”);
res.header(‘Access-Control-Allow-Origin’, “http://locahost:8091”);
res.header(‘Access-Control-Allow-Origin’, “http://localhost:9000”);
res.header(‘Access-Control-Allow-Methods’, ‘GET,PUT,POST,DELETE’);
res.header(‘Access-Control-Allow-Headers’, ‘Origin, X-Requested-With, Content-Type, Accept’);

next();
});

app.set(‘view engine’, ‘html’);
app.set(‘views’, __dirname + ‘/app/views’ );
app.set(‘partials’, __dirname + ‘/app/views/partials’ );

app.engine(‘html’, hbs.__express);

//Register Handlebar Helper.
hbs.registerHelper(“debug”, function(optionalValue) {
console.log(“Current Context”);
console.log("====================");
console.log(this);

if (optionalValue) {
console.log(“Value”);
console.log("====================");
console.log(optionalValue);
}
});

app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// Serve static.
app.use(express.static(__dirname + ‘/public’));
app.use(express.static(__dirname + ‘/public/app’));
app.use(express.static(__dirname + ‘/public/bower’));
require(’./app/amazonServices/routeManager’).setupRoutes(app);
require(’./app/amazonServices/profilePictureManager/profilePictureManager’).init();

require(’./app/controllers/index’)(app);
require(’./app/controllers/subscriptions’)(app);
app.use(’/signup’, bodyParser.json());
app.post(’/signup’, function (req, res) {
console.log(‘its signup time’);

var json = req.body;
var options = {
    url: 'http://127.0.0.1:3985/horsetest/_user/',
    method: 'POST',
    body: json
};

request(options, function(error, response) {
    res.writeHead(response.statusCode);
    res.end();
});

});
app.all("*", function(req, res) {
var url = ‘http://127.0.0.1:3984’ + req.url;
console.log(req.url)
req.pipe(request(url)).pipe(res);
//request(url).pipe(res);
})

app.listen(9000);
console.log(’[Application] is listening on port: 9000’);

//Client code
var db = new PouchDB(“horsetest”);
var remoteCouch = "http://127.0.0.1:9000/horsetest/"
db.replicate.to(remoteCouch, {live:true}, function(e){
console.log(e)
});
db.replicate.from(remoteCouch, {live:true}, function(e){
console.log(e)
});

//The output on the console while logging against console.log(req.url)
“C:\Program Files (x86)\JetBrains\WebStorm 10.0\bin\runnerw.exe” “C:\Program Files\nodejs\node.exe” app.js
aws-sdk Initialized.
[Application] is listening on port: 9000
/api/notifications
/horsetest/_facebook
/horsetest/_facebook
/horsetest/?_nonce=1443648295706
/horsetest/?_nonce=1443648295709
/horsetest/_changes?timeout=25000&style=all_docs&since=0&limit=100&_nonce=1443648295770
/horsetest/_local/bQttFoY6B53pDZ37EEStJQ%3D%3D?_nonce=1443648296038
/horsetest/_changes?timeout=25000&style=all_docs&since=2&limit=100&_nonce=1443648296068
/horsetest/?_nonce=1443648296088
/horsetest/_changes?timeout=25000&style=all_docs&feed=longpoll&since=2&limit=100&_nonce=1443648296089
/horsetest/_revs_diff?_nonce=1443648296111
/horsetest/_local/YRtE4dLpEDOZ27ctzt1yEw%3D%3D?_nonce=1443648296107
/horsetest/_revs_diff?_nonce=1443648296111
/horsetest/_local/YRtE4dLpEDOZ27ctzt1yEw%3D%3D
/horsetest/_local/YRtE4dLpEDOZ27ctzt1yEw%3D%3D
/horsetest/_changes?timeout=25000&style=all_docs&feed=longpoll&since=2&limit=100&_nonce=1443648321151
/horsetest/_changes?timeout=25000&style=all_docs&feed=longpoll&since=2&limit=100&_nonce=1443648346223
/horsetest/_changes?timeout=25000&style=all_docs&feed=longpoll&since=2&limit=100&_nonce=1443648372208
/horsetest/_changes?timeout=25000&style=all_docs&feed=longpoll&since=2&limit=100&_nonce=1443648398210

Can you try using a vanilla XMLHttp request to create the user on :8000/signup and login on :8000/{db}/_session

For the login request for example:

function startSyncGatewaySession(accessToken) {
  var request = new XMLHttpRequest();
  request.open('POST', SYNC_GATEWAY_URL + '/_session', true);
  request.setRequestHeader('Content-Type', 'application/json');
  request.onreadystatechange = function() {
    if (request.readyState == 4 && request.status == 200) {
      console.log('New SG session, starting sync!');
      // start the sync, replication requests should now be authenticated
  };
  request.withCredentials = true;
  request.send(JSON.stringify({"name": name, "password": password}));
}

James

James,
Just a quick question, we are right now creating sessions as per suggestions, but it is not checking the authentication. The documentation clearly states that we have to use your own authentication:
http://developer.couchbase.com/documentation/mobile/1.1.0/develop/guides/sync-gateway/administering-sync-gateway/authenticating-users/index.html#custom--indirect--authentication

I am reading this line from documentation “App server authenticates the credentials however it wants (LDAP, OAuth, and so on).”

From this my understanding is that the api will create the session but we need to check password ourselves?
Thanks

If you call the /_session endpoint on the admin port (4985) then the password isn’t checked. But for logging users in with a name/password you can call /_session on 4984 anyways which is easier.

The /_session endpoint on 4985 is typically used for other auth types as mentioned where you’d want to create a session once you have verified the custom user credentials (access token…).

James

James,

I’m using couchbase lite for Android : https://github.com/couchbase/couchbase-lite-android
I have followed this thread to setup Node server for custom email/password singup.

Now I want to authenticate user using couchbase lite from mobile to direclty on sync gateway and I’m unable to find any suitable api or process that allow me to authenticate user using userid/password.
I have found one link which is : http://developer.couchbase.com/documentation/mobile/current/get-started/get-started-mobile/todo-lite/login-and-sync/index.html

But it also does not explain how to implement login process using couchbase lite, can you please guide me?

Thanks,
Aijaz

Hi @AijazAliShah,

Sorry for the late reply - only just saw this :/.
The simplest way is to use the :4984/_session endpoint with {"name": "james", "password": "letmein"} in the request body. It returns a 401 or 200. If it’s a 200 then you can create a basic authenticator with the createBasicAuthenticator method passing the user name and password. The replicator will take care of renewing the cookie sessions as well when they expire.

James

Thank you @jamiltz for the great answer, This working perfectly for me.

I know it’s a late answer but with latest PouchDB you connect with sync gateway like this:

var remote = new PouchDB("http://user:pass@localhost:4984/bucket/");

sync gateway version is 1.5, not sure if this works with latest sync gateway