Ionic/Couchbase Facebook Login

Hi!
I have moved my app from the original js code of ToDoLite for Phonegap to the new dedicated Ionic Factory.
I have managed to replicate the database with Sync_Gateway using the following replication code:

var remote = {
url : _this.REMOTE_URL
auth = {facebook : {email : _this.user.email}}
}
			divePlanDatabase.replicate(_this.databaseName, remote , true).then(function(result1) {
				//replicate remote to local
				divePlanDatabase.replicate(_this.REMOTE_URL, _this.databaseName, true).then(function(result2) {
					deferred.resolve(result2);
				}, function(error) {
					// There was an error replicating to the local device
					deferred.reject(error);
				});
			}, function(error) {
				// There was an error replicating to the Sync Gateway
				deferred.reject(error);
			});

and by registering the FB token inside ngCouchBaseLite factory with:

this.makeRequest("POST", this.databaseUrl + "_facebook_token", {}, token)

I still have the problem, anyway, that all bulk_get requests sent to the Sync_Gateway after the registration do not appear to have a user, and they are interpreted by the server ad GUEST requests.
How can I set the FB token and user for all requests? Is there any session or cookie that I should use?

Thanks for your help!
Luca

Hey @lucapale,

A few questions here:

  1. Are you registering the Facebook token before you try to replicate?
  2. What does token look like in your request?
  3. Does the register request return a success?

I’ll see if I can help you after that.

Best,

My sequence for the login is:
1- get FB token
2- create local/user
3- register FB token (same token received from Facebook : “CAAHArxqPE7QBANt1SrDTUykm2ifCoQrZBmfXLebqDs3Gz9ZBb0nUSyHnq2k984QBMuTaQehVezl2ojm…”)
4- receive ok from server:
"fbtoken res: {“email”:“luca.palezza@gmail.com”,“ok”:“registered”}"
4- create user profile in the database
5- replicate DB with response: {“session_id”:“repl002”}
All this seems to run smoothly, because I can see from the Sync_Gateway log that the correct user is logging in:

2015-09-24T16:49:46.731+02:00 HTTP:  #278: POST /diveplanapp/_facebook
2015-09-24T16:49:46.762+02:00 HTTP:  #279: GET /diveplanapp/_changes?feed=websocket
2015-09-24T16:49:46.888+02:00 HTTP:  #280: GET /diveplanapp/_local/c149075c580f7049112e5767d1db24a77acc58a8  (as luca.palezza@gmail.com)
2015-09-24T16:49:46.899+02:00 HTTP: #280:     --> 404 missing  (11.4 ms)
2015-09-24T16:49:46.936+02:00 HTTP:  #281: POST /diveplanapp/_bulk_get?revs=true&attachments=true
2015-09-24T16:49:46.937+02:00 HTTP:  #282: POST /diveplanapp/_revs_diff  (as luca.palezza@gmail.com)
2015-09-24T16:49:47.009+02:00 HTTP:  #283: POST /diveplanapp/_bulk_get?revs=true&attachments=true
2015-09-24T16:49:47.011+02:00 HTTP:  #284: POST /diveplanapp/_bulk_get?revs=true&attachments=true

The problem is that all the bulk_get do not come from the same user, but from a GUEST, so I cannot receive the correct data from the server.

I add also my login code for your reference:

checkUser: function() {
			 var deferred = $q.defer();
			 var _this = this
 		 	//check if user is already registered in the database
 		   divePlanDatabase.getLocalDocument("user").then(function(local_user) {
 		    	log("local_user",local_user)
 				deferred.resolve(local_user);
				//register new token
				
 		   }, function(err){
 		    	log("local_user non existing",err)
 		 		//create user
 		       divePlanDatabase.createLocalDocument("user",_this.user).then(function(result) {
 		 			 log("local_user",result,_this.user)
 		           // Document created successfully
					 //create user profile
	 				_this.registerFacebookToken().then(function(ok) {
	 					log("fbtoken res",JSON.stringify(ok))
		 				_this.createMyProfile().then(function(profile) {
		 					log("profile created",JSON.stringify(profile))
		 					deferred.resolve(profile);
		 				}, function(error) {
		 					// There was an error replicating to the local device
		 					log("createMyProfile error",error)
		 					deferred.reject(error);
		 				});
	 				}, function(error) {
	 					// There was an error replicating to the local device
	 					log("registerFacebookToken error",error)
	 					deferred.reject(error);
	 				});
 					
 		       }, function(error) {
 		           // Document creation 
 		 			 log("local_user error",error)
 					 deferred.reject(error);
 		       });
 		   });
 			return deferred.promise;
		 },
		 createMyProfile : function() {
				var _this = this
				//log("createMyProfile user "+JSON.stringify(config.user))
				var profileData = angular.copy(_this.user)
				profileData.type = "profile"
				profileData.user_id = profileData.email
				delete profileData.email
				//log("createMyProfile", profileData)
			   return divePlanDatabase.updateDocument("p:"+profileData.user_id, profileData)
		 },
		 registerFacebookToken : function() {
				var _this = this
				var registerData = {
					remote_url : _this.REMOTE_SERVER,
					email : _this.user.email,
					access_token : _this.user.access_token
				}
				log("registerFacebookToken",registerData)
				return divePlanDatabase.updateFBToken(registerData)
		 },
		 replicateDatabase: function() {
			 var deferred = $q.defer();
			 var _this = this
			//replicate local to remote
		    var remote = {
		        url : _this.REMOTE_URL
			 }
			 if (_this.user && _this.user.email) {
			 	remote.auth = {facebook : {email : _this.user.email}}
			 }
			 log("remote",remote)
			divePlanDatabase.replicate(_this.databaseName, remote , true).then(function(result1) {
				log("replicate local to remote",_this.databaseName, remote,result1)
				//replicate remote to local
				divePlanDatabase.replicate(_this.REMOTE_URL, _this.databaseName, true).then(function(result2) {
					log("replicate remote to local",result2)
					deferred.resolve(result2);
				}, function(error) {
					// There was an error replicating to the local device
					log("replicate error",error)
					deferred.reject(error);
				});
			}, function(error) {
				// There was an error replicating to the Sync Gateway
				log("replicate error",error)
				deferred.reject(error);
			});
			return deferred.promise;
		 },
		 guestLogin: function(user) {
			 var _this = this
			 if (user) {
				 //existing guest logi
			 	_this.user = user
				 guestReplicate()
			 } else {
				 //new guest login
				 _this.user = {
					 guest: true
				 }
		       divePlanDatabase.createLocalDocument("user",_this.user).then(function(result) {
		 			 log("guest_user",result)
					 guestReplicate()
		       }, function(error) {
		           // Document creation 
		 			 log("guest_user error",error)
		       });
			 }
			 
			 function guestReplicate() {
				_this.replicateDatabase().then(function(ok){
					log("replicateDatabase guest",ok)
					$state.go('app.sites')
				}, function(err){
					log("replicateDatabase guest err",err)
				})
			 }
	       
		 },
		 login: function() {
			 var _this = this
		 	/*
		 	Get user email address from Facebook, and access code to verify on Sync Gateway
		 	*/
		 	var result = JSON.parse(window.localStorage.getItem('ionFB_user'))
		 	_this.user = result.profileInfo
		 	_this.user.access_token = result.authResponse.accessToken;
		 	_this.user.user_id= _this.user.email
		   log("login",_this.user)
			//check and ask user email
			if (!_this.user.email) {
				_this.requireEmail()
			} else {
				_this.checkUser().then(function(res){
					log("checkUser OK",JSON.stringify(res))
					//replicated DB
					_this.replicateDatabase().then(function(ok){
						log("replicateDatabase",ok)
						$state.go('app.sites')
					}, function(err){
						log("replicateDatabase err",err)
					})
				}, function(err){
					log("checkUser err",err)
				})
			}
		 }

Thank you very much for your support.
Luca

By talking directly to Sync Gateway you’ve set an HTTP session cookie in the context your requests get sent in. But that doesn’t mean that the Couchbase Lite replicator will use the same cookies.

I can’t see anywhere that you said what platform of Couchbase Lite you’re using. On iOS, we use a custom cookie store so that each replication can have its own independent session authentication. So cookies you set as a side effect of an XHR will not be used by the replicator.

Anyway, this isn’t the supported way to do Facebook auth. (Our documentation is really, really lacking here, and I apologize for that.) Here are some notes I wrote earlier about it:

To use Facebook auth, you specify the source or target of the replication like this:

	source: {
		url: “https://server.com/db”
		auth: {
			“facebook”: {
				email: “foo@example.com”

That just specifies the Facebook account to use; the auth token has to be registered separately (for historic reasons that really no longer apply…) You first register the token with a POST to /_facebook_token whose body looks like

{
	remote_url: “https://server.com/db”
	email: “foo@example.com”
	access_token: “xxxxxxxxxxxxx”
}

HI. I am already doing this both for replication remote URL and for token POST, but the problem is still there.
I am using Ionic on both platforms, iOS and Android, the problem is the same for both.

Previously I was using jChris code inside ToDoLite with “coax” for the replication, but I cannot really understand all the code there, so I don’t know where there could be a difference between his code and ngCouchBaseLite for Ionic.

Still, all requests sent by the app after replication do not bring the user’s account with them and they are considered by SyncGateway as GUEST requests.

Any other suggestion by @nraboy that made the tutorial?
Thanks
Luca

Hi @lucapale,

Sorry about the documentation missing those parameters. We’re fixing that.

Here’s the list of API calls to make it work. Note you’ll have to replace http://localhost:5984/db with the hostname of your CBL Listener instance. You can get it with ngCouchbaseLite. I believe you can make those requests work with the makeRequest method of ngCouchbaseLite. In the Sync Gateway logs, you should see the (as facebook_id) message show up instead of as GUEST. And see the user was registered in the Sync Gateway admin UI:

// 1. Create database
PUT /db HTTP/1.1
Host: localhost:5984
Connection: close
User-Agent: Paw/2.2.1 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 0

// 2. Register user
POST /_facebook_token HTTP/1.1
Content-Type: application/json
Host: localhost:5984
Connection: close
User-Agent: Paw/2.2.1 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 307

{
"access_token":"CAACEdEose0cBACyOox9aZCOLZCCLZBU0OLKNzDgx3aSmSSYkW2aaNzPO85lG2ypaZCYZCAcdfXU3ziVrqIydrmuyZBktsMmeVxBSig8MKbZCoZA0GIErv1iUZCATU7jOqfdS8r9gfFv3fnxOsIKMuAZCGNvMPm68oJvb7UM0x51ZBlNx1yUd8sxeFlbqYfKEhL8so7fQcdnSoPqPg5QwFiOhAvd",
"email":"jamesn@couchbase.com",
"remote_url":"http://localhost:4984"
}

// 3. Insert docs
POST /db/_bulk_docs HTTP/1.1
Content-Type: application/json
Host: localhost:5984
Connection: close
User-Agent: Paw/2.2.1 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 72

{
"docs":[{"name":"chef123"},{"city":"London"},{"city":"San Francisco"}]
}

// 4. Replicate with Facebook auth of registered user
POST /_replicate HTTP/1.1
Content-Type: application/json
Host: localhost:5984
Connection: close
User-Agent: Paw/2.2.1 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 135

{
"target":{"url":"http://localhost:4984/todos","auth":{"facebook":{"email":"jamesn@couchbase.com"}}},
"source":"db",
"continuous":"true"
}

Thank you for your reply @jamiltz.
This is actually what I already do and the user login works correctly.
What does not work is the replication of the database with the logged in user (even if I follow all your instructions above).
As you can see from my Sync_Gateway log, the first POST of revs_diff is coming from the logged in user, but the next request bulk_get is not coming from this user, and the Sync_Gateway is interpreting this as GUEST user and return only the “public” channels and not the “private” channels associated to that user.

2015-09-24T16:49:46.937+02:00 HTTP:  #282: POST /diveplanapp/_revs_diff  (as luca.palezza@gmail.com)
2015-09-24T16:49:47.009+02:00 HTTP:  #283: POST /diveplanapp/_bulk_get?revs=true&attachments=true

For example, how could I make direct requests to SyncGateway server without using the replication and with an authenticated FB user?

i.e. $http({method:"GET", url:"http://my_SyncGateway_server:4984/dbName/doc_id", withCredentials:true})

I noticed that if I use “http://user:pass@my_SyncGateway_server:4984” with a known user, instead of the FB user, then I can get all the replication correct for this know user. The problem is that I need to do this with a FB user…
Thanks for your help!
Luca

Under the hood, the replicator creates a session using the /_facebook endpoint.
For future requests, it then uses the session cookie in the request header.

The _bulk_get request is used in pull replications. And the code snippet I pasted above is for a push replication. Below are some requests to add documents as a particular user and start a pull replication with the Listener. The user only has access to the san-francisco and london channels so 2 documents out of 3 are replicated to Couchbase Lite.

// 1. Create database
PUT /db HTTP/1.1
Host: localhost:5984
Connection: close
User-Agent: Paw/2.2.1 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 0

// 2. Create Sync Gateway session
POST /todos/_facebook HTTP/1.1
Content-Type: application/json
Cookie: SyncGatewaySession=2fd1366d719db1416a5d8101d9c188bfea65595a
Host: localhost:4984
Connection: close
User-Agent: Paw/2.2.1 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 304

{
"access_token":"CAACEdEose0cBAIWIDrNYs9ygNe9VeAxMEAfc1yZCDxDaVCcxOZBuUWqibkTcLnZBSFveMVrdJznkMJPGfEYYcf5XzP4EIJtI7c3cyebaGHQMO3H4klyKbZCLShoO3ks8TXhUKiK9ZA9pTJgRFhetVpBltqhFW861l1B629ZAtjFb45TETixCOQgDbIneVdgg4vgSu1HPXgVj7ZB6lUKZAwwQ",
"email":"jamesn@couchbase.com",
"remote_url":"http://localhost:4984"
}

// 3. Add documents to Sync Gateway with the cooke from step 2
POST /todos/_bulk_docs HTTP/1.1
Cookie: SyncGatewaySession=3be89cb8bff76bdf2e2b67a8853963bb6dfdf3d8; Path=/todos/; Expires=Sun, 27 Sep 2015 16:21:05 UTC
Content-Type: application/json
Host: localhost:4984
Connection: close
User-Agent: Paw/2.2.1 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 151

{
"docs": [{"city":"new-york","user_id":"chef123"},{"city":"london","user_id":"1423642354624121"},{"city":"san-francisco","user_id":"1423642354624121"}]
}

// 4. Register Facebook token on CBL Listener
POST /_facebook_token HTTP/1.1
Content-Type: application/json
Host: localhost:5984
Connection: close
User-Agent: Paw/2.2.1 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 304

{
"access_token":"CAACEdEose0cBAIWIDrNYs9ygNe9VeAxMEAfc1yZCDxDaVCcxOZBuUWqibkTcLnZBSFveMVrdJznkMJPGfEYYcf5XzP4EIJtI7c3cyebaGHQMO3H4klyKbZCLShoO3ks8TXhUKiK9ZA9pTJgRFhetVpBltqhFW861l1B629ZAtjFb45TETixCOQgDbIneVdgg4vgSu1HPXgVj7ZB6lUKZAwwQ",
"email":"jamesn@couchbase.com",
"remote_url":"http://localhost:4984"
}

// 5. Pull replication from Sync Gateway to CBL
POST /_replicate HTTP/1.1
Content-Type: application/json
Host: localhost:5984
Connection: close
User-Agent: Paw/2.2.1 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 115

{
"source":{"url":"http://localhost:4984/todos",
"auth":{"facebook":{"email":"jamesn@couchbase.com"}}},
"target":"db"
}

Got it! That’s the point I was missing:

Replicate with Facebook auth of registered user
{
"target":{"url":"http://localhost:4984/todos","auth":{"facebook":{"email":"jamesn@couchbase.com"}}},
"source":"db",
"continuous":"true"
}
Pull replication from Sync Gateway to CBL
{
"source":{"url":"http://localhost:4984/todos","auth":{"facebook":{"email":"jamesn@couchbase.com"}}},
"target":"db"
}

In my “source” for the replication to CBL I was missing the “auth” parameter, because I thought the auth was required only in the push replication when you register the user.
Thank you for your support! Really appreciated!
Don’t forget to update the documentation on this ;).
Bye
Luca

Another question @jamiltz related to the API.
If I want to create a web service that communicates directly with the SyncGateway without the use of database replication (just a simple Angular/NodeJs app), how can I get the Session Cookie for the REST requests (alway using the FB authentication process)? Just posting directly to the server a POST /todos/_facebook?
Thanks,
Luca

1 Like

Yes the POST request to /{db}/_facebook should set the cookie header for future requests to the sync gateway rest api.

I realised that I forgot to paste the config file I was using in the example above:

{
  "log": ["CRUD", "REST+", "Access"],
  "facebook": {"register": true},
  "databases": {
    "todos": {
      "server": "walrus:",
      "users": {
        "GUEST": {"disabled": true}
      },
      "sync": 
  	`
      function(doc, oldDoc) {
        channel(doc.city);
        access(doc.user_id, doc.city);
      }
    `
    }
  }
}

Hi again @jamiltz !
Now I am testing my app under Android and I see there are again replication problems.
The same code works in iOS, but running when the app under Android the login process does not work.
Steps in Android:

  • When I register the facebook_token I receive:

    {ok: “registered”}

  • first replication POST and received log:

    {
    continuous: true;
    source: “local_db_name”,
    target: { auth: { facebook: {email: "luca.palezza@gmail.com"} , url: “http://sync_gateway_url:4984/db_name”}
    }
    log: {session_id: “repl001”}

  • second replication POST and received log:
    {
    continuous: true;
    target: “local_db_name”,
    source: { auth: { facebook: {email: "luca.palezza@gmail.com"} , url: “http://sync_gateway_url:4984/db_name”}
    }
    log: {session_id: “repl002”}

I noticed from the Sync_Gateway log that there is only a request to GET session, but not a POST _facebook, like it happens in iOS. The result is that the bulk_get does not have a user:

2015-09-29T14:34:49.686+02:00 HTTP:  #5571: POST /diveplanapp/_bulk_get?revs=true&attachments=true

But if I post data to the server then the user is the correct one.
It seems that the replication from local to remote is working, while the opposite has some problem.
Any suggestion on this?
Thanks
Luca

Hey @jamiltz / @nraboy / @jens , any feedback on this?
Thanks!

@lucapale I’m not sure why it wouldn’t work on Android if it’s working on iOS.
Would it be possible to post a sample cordova app project with the code of the push replication that isn’t working as expected so we can take a look? That would really help to debug this issue.

Thanks
James

Sure! I’ll do it tomorrow and send you the link.