Passport Facebook integration with angular-fullsta

2019-07-12 04:44发布

问题:

I have an angular-fullstack app generated from here -

https://github.com/angular-fullstack/generator-angular-fullstack

I am using the same directory structure as angular-fullstack.

Now I am trying to authenticate users with facebook sdk and did the following steps -

1) specify passport facebook login strategy

// created auth/facebook/index.js

'use strict';

var express = require('express');
var passport = require('passport');
var auth = require('../auth.service');

var router = express.Router();

router
  .get('/', passport.authenticate('facebook', {
    scope: ['email', 'public_profile', 'user_friends', 'user_events'],
    failureRedirect: '/',
    session: false
  }))

  .get('/callback', passport.authenticate('facebook', {
    failureRedirect: '/',
    session: false
  }), auth.setTokenCookie);

module.exports = router;



// created auth/facebook/passport.js

var passport = require('passport');
var FacebookStrategy = require('passport-facebook').Strategy;
var config = require('../../config/environment');
var jwt = require('jsonwebtoken');


exports.setup = function (User, config) {
  passport.use(new FacebookStrategy({
      clientID: config.facebook.clientID,
      clientSecret: config.facebook.clientSecret,
      callbackURL: config.facebook.callbackURL
    },
    function(accessToken, refreshToken, profile, done) {
      User.findOne({'facebookId':profile.id}, function(err, user){
        if(err) return done(err);
        if(user) {
          return done(null, user);
        } else {
          var newUser = {};
          newUser['facebookId'] = profile.id;
          newUser['providerData'] = {
            name: 'facebook',
            username: profile.username,
            displayName: profile.displayName,
            gender: profile.gender,
            profileUrl: profile.profileUrl
          };
          newUser['name'] = profile.name.givenName ? profile.name.givenName: '';
          newUser['email'] = profile.emails.length>0? profile.emails[0].value : done('email not found');
          function generatePassword() {
            var length = 8,
              charset = "abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
              retVal = "";
            for (var i = 0, n = charset.length; i < length; ++i) {
              retVal += charset.charAt(Math.floor(Math.random() * n));
            }
            return retVal;
          }

          newUser['password'] = generatePassword();
          newUser['role'] = 'user';
          var user = new User(newUser);
          user.save(function (err, user) {
            if (err) { console.log(err); done(err); }
            var token = jwt.sign({_id: user._id }, config.secrets.session, { expiresInMinutes: 60 * 5 });
            res.json({ token: token });
          });
        }
      });
    }
  ));
};

// added entry in auth/index.js  for facebook module

'use strict';

var express = require('express');
var passport = require('passport');
var config = require('../config/environment');
var User = require('../api/user/user.model');

// Passport Configuration
require('./local/passport').setup(User, config);
require('./facebook/passport').setup(User, config);

var router = express.Router();

router.use('/local', require('./local'));
router.use('/facebook', require('./facebook'));

module.exports = router;

In client side I made the following changes -

// installed ng-facebook from https://github.com/GoDisco/ngFacebook using bower install ng-facebook
// added ngFacebook in Angular App module

// set App Id in app.config - $facebookProvider.setAppId('XXXXXXXXXXXX');

Then added this - in app.run

app.run(function ($rootScope, $location, Auth) {

    (function (d, s, id) {
      var js, fjs = d.getElementsByTagName(s)[0];
      if (d.getElementById(id)) {
        return;
      }
      js = d.createElement(s);
      js.id = id;
      js.src = "//connect.facebook.net/en_US/sdk.js";
      fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));

    // Redirect to login if route requires auth and you're not logged in
    $rootScope.$on('$stateChangeStart', function (event, next) {
      Auth.isLoggedInAsync(function (loggedIn) {
        if (next.authenticate && !loggedIn) {
          $location.path('/access/signin');
        }
      });
    });
  })

Then finally calling /auth/facebook from my client now I am getting the data from facebook after a user logs in and I am able to save it in Database, but the homepage always gets redirected to login state and not the dashboard.

I have the following http interceptors in my client app -

app.factory('authInterceptor', function ($rootScope, $q, $cookieStore) {
    return {
        // Add authorization token to headers
        request: function (config) {
            config.headers = config.headers || {};
            if ($cookieStore.get('token')) {
                config.headers.Authorization = 'Bearer ' + $cookieStore.get('token');
            }
            return config;
        },

        // Intercept 401s and redirect you to login
        responseError: function (response) {
            if (response.status === 401) {
                $cookieStore.remove('token');
                return $q.reject(response);
            }
            else if (response.status === 403) {
                $cookieStore.remove('token');
                return $q.reject(response);
            } else if (response.status === 405) {
                $cookieStore.remove('token');
                return $q.reject(response);
            }
            else {
                return $q.reject(response);
            }
        }
    };
})

Now after I login with my facebook account, when I receive the callback from facebook. It is getting redirected to the same login state again even when the API /api/users/me is giving me the logged in information in my browser console -

{"_id":"569bc8d6b0c2e8315539539e","facebookId":"XXXXX","name":"Harshit","email":"XXXX","__v":0,"providerData":{"name":"facebook"},"messages":[],"notifications":[],"subjects":[],"date":"2016-01-17T17:01:10.000Z","role":["user"]}

So, I am thinking passport is not setting the authorization headers properly or there is something other than my http interceptor that is redirecting me to the login page only

How can I debug this issue or find out where I am going wrong, or missing something ?

回答1:

In my opinion I prefer just checking whether the server still has your sesssion information.

In your routing you can do check

when('/url', {
templateUrl: 'partial',
controller: 'controller',
resolve: { loggedin: checkLoggedin}}).

Just create your function checkLoggedin like this

  var checkLoggedin = function($q, $http, $location){
    var deferred = $q.defer();

    $http.get('/loggedin').then(function(response){
      deferred.resolve();
    },function(response){
      deferred.reject();
      $location.path('/home');
    });

    return deferred.promise;
  }

Basically your making a promise here. Your saying here go to the backend and check I will wait for the backend to send me back a response. If the response is 200 let them on the page or else redirect to home.

Your back-end should return a 401 (unauthorize) or 200 (success) response letting your front-end know whether to let the user on the page.

function(req,res){

  if (req.isAuthenticated() == true) {
    res.status(200).send("Authenticate.");
  }else{
    res.status(401).send("Not Authenticate");
  }

}

You can check more info about resolve here

  • Angular Route Provider

The resolve will run all dependencies that is passed to it before loading your view.

This will allow you to make a http request to your back end and verify with passport to see if the session is still there.

Angular has to make a http request to the backend everytime to make sure that the user is indeed logged in.