DoctorPangloss
DoctorPangloss

Reputation: 3073

How can I login to Meteor with native device Facebook?

Suppose I logged into my device's Facebook authentication, like system Facebook on iOS. I obtain an access token.

How can I use the access token to login to Meteor's Facebook Oauth provider?

Upvotes: 2

Views: 2033

Answers (4)

Allreadyhome
Allreadyhome

Reputation: 1262

This answer could be improved further as we can now directly debug the token from a REST http request using futures. Credit still goes to @DoctorPangloss for the principal steps necessary.

//Roughly like this - I removed it from a try/catch

        var future = new Future();
        var serviceData = {
              accessToken: accessToken,
              email: email
            };

            var input = Meteor.settings.private.facebook.id + '|' + Meteor.settings.private.facebook.secret
            var url = "https://graph.facebook.com/debug_token?input_token=" + accessToken + "&access_token=" + input
            HTTP.call( 'GET', url, function( error, response ) {

                      if (error) {
                          future.throw(new Meteor.Error(503, 'A error validating your login has occured.'));
                      }

                      var info = response.data.data

                      if (!info.is_valid) {
                          future.throw(new Meteor.Error(503, 'This access token is not valid.'));
                      }

                      if (info.app_id !== Meteor.settings.private.facebook.id) {
                          future.throw(new Meteor.Error(503, 'This token is not for this app.'));
                      }

                      // Force the user id to be the access token's user id
                      serviceData.id = info.user_id;

                      // Returns a token you can use to login
                      var user = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, {});
                      if(!user.userId){
                        future.throw(new Meteor.Error(500, "Failed to create user"));
                      }

                      //Add email & user details if necessary 
                      Meteor.users.update(user.userId, { $set : { fname : fname, lname : lname }})
                      Accounts.addEmail(user.userId, email)

                      //Generate your own access token!
                      var token = Accounts._generateStampedLoginToken()
                      Accounts._insertLoginToken(user.userId, token);

                      // Return the token and the user id
                      future.return({
                        'x-user-id' : user.userId,
                        'x-auth-token' : token.token
                      })
            });

          return future.wait();

Use this instead of the JS lib suggested by @DoctorPangloss. Follow the same principles he suggested but this avoids the need to integrate an additional library

Upvotes: 0

DoctorPangloss
DoctorPangloss

Reputation: 3073

To login with Facebook using an access token obtained by another means, like iOS Facebook SDK, define a method on the server that calls the appropriate Accounts method:

$FB = function () {
    if (Meteor.isClient) {
        throw new Meteor.Error(500, "Cannot run on client.");
    }

    var args = Array.prototype.slice.call(arguments);
    if (args.length === 0) {
        return;
    }
    var path = args[0];
    var i = 1;
    // Concatenate strings together in args
    while (_.isString(args[i])) {
        path = path + "/" + args[i];
        i++;
    }

    if (_.isUndefined(path)) {
        throw new Meteor.Error(500, 'No Facebook API path provided.');
    }
    var FB = Meteor.npmRequire('fb');

    var fbResponse = Meteor.sync(function (done) {
        FB.napi.apply(FB, [path].concat(args.splice(i)).concat([done]));
    });

    if (fbResponse.error !== null) {
        console.error(fbResponse.error.stack);
        throw new Meteor.Error(500, "Facebook API error.", {error: fbResponse.error, request: args});
    }

    return fbResponse.result;
};

Meteor.methods({
    /**
     * Login to Meteor with a Facebook access token
     * @param accessToken Your Facebook access token
     * @returns {*}
     */
    facebookLoginWithAccessToken: function (accessToken) {
        check(accessToken, String);

        var serviceData = {
            accessToken: accessToken
        };

        // Confirm that your accessToken is you
        try {
            var tokenInfo = $FB('debug_token', {
                input_token: accessToken,
                access_token: Meteor.settings.facebook.appId + '|' + Meteor.settings.facebook.secret
            });
        } catch (e) {
            throw new Meteor.Error(500, 'Facebook login failed. An API error occurred.');
        }

        if (!tokenInfo.data.is_valid) {
            throw new Meteor.Error(503, 'This access token is not valid.');
        }

        if (tokenInfo.data.app_id !== Meteor.settings.facebook.appId) {
            throw new Meteor.Error(503, 'This token is not for this app.');
        }

        // Force the user id to be the access token's user id
        serviceData.id = tokenInfo.data.user_id;

        // Returns a token you can use to login
        var loginResult = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, {});

        // Login the user
        this.setUserId(loginResult.userId);

        // Return the token and the user id
        return loginResult;
    }
}

This code depends on the meteorhacks:npm package. You should call meteor add meteorhacks:npm and have a package.json file with the Facebook node API: { "fb": "0.7.0" }.

If you use demeteorizer to deploy your app, you will have to edit the output package.json and set the scrumptious dependency from "0.0.1" to "0.0.0".

On the client, call the method with the appropriate parameters, and you're logged in!

In Meteor 0.8+, the result of Accounts.updateOrCreateUserFromExternalService has changed to an object containing {userId: ...} and furthermore, no longer has the stamped token.

Upvotes: 8

Vincil Bishop
Vincil Bishop

Reputation: 1624

Building on DrPangloss' most excellent answer above, combining it with this awesome post: http://meteorhacks.com/extending-meteor-accounts.html

You'll run into some issues using ObjectiveDDP in trying to get the client persist the login. Include the header:

#import "MeteorClient+Private.h"

And manually set the required internals. Soon I'll make a meteorite package and an extension to MyMeteor (https://github.com/premosystems/MyMeteor) but for now it's manual.

loginRequest: {"accessToken":"XXXXXb3Qh6sBADEKeEkzWL2ItDon4bMl5B8WLHZCb3qfL11NR4HKo4TXZAgfXcySav5Y8mavDqZAhZCZCnDDzVbdNmaBAlVZAGENayvuyStkTYHQ554fLadKNz32Dym4wbILisPNLZBjDyZAlfSSgksZCsQFxGPlovaiOjrAFXwBYGFFZAMypT9D4qcZC6kdGH2Xb9V1yHm4h6ugXXXXXX","fbData":{"link":"https://www.facebook.com/app_scoped_user_id/10152179306019999/","id":"10152179306019999","first_name":"users' first name","name":"user's Full Name","gender":"male","last_name":"user's last name","email":"[email protected]","locale":"en_US","timezone":-5,"updated_time":"2014-01-11T23:41:29+0000","verified":true}}

Meteor.startup(
  function(){
    Accounts.registerLoginHandler(function(loginRequest) {
      //there are multiple login handlers in meteor.
      //a login request go through all these handlers to find it's login hander
      //so in our login handler, we only consider login requests which has admin field

      console.log('loginRequest: ' + JSON.stringify(loginRequest));

      if(loginRequest.fbData == undefined) {
        return undefined;
      }

      //our authentication logic :)
      if(loginRequest.accessToken == undefined) {
        return null;
      } else {
        // TODO: Verfiy that the token from facebook is valid...
        // https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.0#checktoken
        // graph.facebook.com/debug_token? input_token={token-to-inspect}&access_token={app-token-or-admin-token}

      }

      //we create a user if not exists, and get the userId
      var email = loginRequest.fbData.email || "-" + id + "@facebook.com";
      var serviceData = {
        id: loginRequest.fbData.id,
        accessToken: loginRequest.accessToken,
        email: email
      };
      var options = {
        profile: {
          name: loginRequest.fbData.name
        }
      };
      var user = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, options);

      console.log('Logged in from facebook: ' + user.userId);

      //send loggedin user's user id
      return {
        userId: user.userId
      }
    });
  }
);

Upvotes: 1

Tarang
Tarang

Reputation: 75945

You can get the accessToken in the Meteor.user() data at Meteor.user().services.facebook.accessToken (be aware this can only be accessed on the server side as the services field is not exposed to the client.

So when a user logs in with facebook on your meteor site these fields would be populated with the user's facebook data. If you check your meteor user's database with mongo or some other gui tool you could see all the fields which you have access to.

Upvotes: 1

Related Questions