Angular/Node/Express/Passport Cross Domain Problems - Enable CORS Passport Facebook Authentication
Asked Answered
P

3

26

It's been two days and a million tries to enable CORS when trying to authenticate a user with Facebook using Passport in NodeJS/Express.

The error I get on Chrome is this:

XMLHttpRequest cannot load https://www.facebook.com/dialog/oauth?response_type=code&redirect_uri=http%…%3A8080%2Fauth%2Ffacebook%2Fcallback&scope=email&client_id=598171076960591. 
No 'Access-Control-Allow-Origin' header is present on the requested resource. 
Origin 'http://localhost:8080' is therefore not allowed access. 

The routes I use are as simple as that:

// =====================================
// FACEBOOK ROUTES =====================
// =====================================
// route for facebook authentication and login

app.get('/auth/facebook', passport.authenticate('facebook', { scope : 'email' }));

// handle the callback after facebook has authenticated the user
app.get('/auth/facebook/callback',
    passport.authenticate('facebook', {
        successRedirect : '/home',
        failureRedirect : '/login'
    }));

This is how the route is called on my angularJS file (I've also tried setting withCredentials : true):

$http.get('/auth/facebook')
    .success(function(response) {

    }).error(function(response){

    });

I've tried a dozen solutions that I found here on StackOverflow and other forums.

  1. I tried adding this on the before my routes on the routes.js files:

    app.all('*', function(req, res, next) {
      res.header('Access-Control-Allow-Origin', '*');
      res.header("Access-Control-Allow-Headers", "Content-Type,X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5,  Date, X-Api-Version, X-File-Name");
      res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,HEAD,DELETE,OPTIONS');
      res.header('Access-Control-Allow-Credentials', true);
    
      if ('OPTIONS' == req.method) {
          res.send(200);
      } else {
          next();
      }
    });
    
  2. I tried adding this on server.js file (note that I changed header to setHeader but I've tried both):

    app.use(function(req, res, next) {
      res.setHeader('Access-Control-Allow-Origin', '*');
      res.setHeader('Access-Control-Allow-Headers', 'Content-Type,X-Requested-With');
      res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,HEAD,DELETE,OPTIONS');
      res.setHeader('Access-Control-Allow-Credentials', true);
    
      if ('OPTIONS' == req.method) {
        res.send(200);
      } else {
        next();
      }
    
     });
    
     require('./app/routes.js')(app, passport);
    
  3. I tried adding this on my app.js file (angularJS configurations):

    $httpProvider.defaults.useXDomain = true;
    delete $httpProvider.defaults.headers.common['X-Requested-With'];
    $httpProvider.defaults.withCredentials = true;
    

Anyway, I don't know what else to do. Everything I found online didn't work. Is there a chance it has something to do with me using AngularJS Routing? I don't see any reason why this would matter, but I kinda ran out of guesses.

My situation is very similar to this one: Angular/Node/Express/Passport - Issues when connecting to facebook(CORS)

Thanks in advance!

Plaided answered 25/9, 2014 at 2:39 Comment(5)
possible duplicate of How to allow CORS in Express/NodeJS?Nucleonics
I know there are a lot of similar questions, and I've seen almost all of them. Unfortunately, none of the solutions I tried have worked so far...Plaided
You can not use both Access-Control-Allow-Credentials: true and Access-Control-Allow-Origin: *' in the same time. For your case, you should set Access-Control-Allow-Origin: localhost:8080 instead of *.Everyplace
hi @LarissaLeite, did you ever get this to work with anguar? I'm trying now and am running into your problems.Cataclinal
@LarissaLeite did you find the solution ? It's been two years you posted this but I'm facing this issue now. ThanksFishworm
T
38

I was having this issue and almost reached the point where I was convinced I could find no solution, but looking at a simple tutorial again (http://mherman.org/blog/2013/11/10/social-authentication-with-passport-dot-js/) solved it for me. I was trying to make an API call from Angular to Node.js, which is going to always bring you those XMLHttpRequest errors despite what you configure on the server, CORS or not! CORS is not the fixture - if you opened your Chrome network console, you'll find that your request to Google or Facebook or whatever 3rd party site is out of your control to change - it was triggered from a 302 redirect that was sent back to your frontend, something that Angular.js or any other framework has no power to control, thus you can't really add "Access Control Allow Origin" to that request anyway.

The solution is simply to make the button or text that says "Sign In with _____" a LINK. A literal <a href="/auth/facebook"></a> link. That's it.

Of course, I also met with a lot of other stumbling blocks and gotchas in the process. I tried to not use the default middleware for passport.authenticate('facebook'), but tried to wrap it in a function(req, res, next){ ... }, thinking that would do something, but it doesn't.

Toilet answered 20/2, 2015 at 13:15 Comment(10)
I'm using UI-Router. I tried the link, but that's being caught by $urlRouterProvider.otherwise('/'); and taking me to my home page. Any thoughts?Vereeniging
@AdamZerner, it is simply because the link you tried was not valid "Frontend URL"(or say not angular router recognized URL) and that's where $urlRouterProvider.otherwise('/'); kicked in. The solution is actually quite simple, you just need to add ' target="_self" ' to the a tag, which will bypass angular router.Yandell
Hey, I got stuck on the exact same issue as the original question. The combination of Gary's answer and spiritwalker's comment fixed all issues related to the CORS errors and redirects. Worked like a charm. Thank you @Toilet and spiritwalker.Progenitor
How do you send user object or token back to front-end?Quadri
If you don't use a service that makes the http call. How can you retrieve the token client-side ?Numbers
@Quadri do you have a good way to return the token to the client? I've just asked a question about it: #49882064Jetty
@Jetty actually I don't send tokens back to front-end. I use withCredentials parameter in ajax callsQuadri
@Matt, Thanks so much for your prompt reply. Do you perhaps have a small example you could share?Jetty
@Jetty it definitely works for localstrategy, but I am not sure about facebook, because they changed it since I left the project. pastebin.com/svuubYDA pastebin.com/gR8e14SX/?e=1 pastebin.com/RJdp4j5qQuadri
@Matt, thanks a lot for the examples, truly appreciate it.Jetty
A
3

I had also a problem with facebook login and cors but not with Passport in NodeJS/Express, my application is java(spring mvc)+ angular. I've managed to pass over the problem modifying my initial fb login function:

$scope.loginWithFacebook = function () {
    $http({
        method: 'GET',
        url: 'auth/facebook',
        dataType: 'jsonp'
    }).success(function(data){
        console.log('loginWithFacebook success: ', data);
    }).error(function(data, status, headers, config) {
        console.log('loginWithFacebook error: ', data);
    });
}

with

$scope.loginWithFacebook = function() {
    $window.location = $window.location.protocol + "//" + $window.location.host + $window.location.pathname + "auth/facebook";
};

Hope it helps!

Apopemptic answered 15/10, 2014 at 7:29 Comment(2)
@mikestaub you cannot call server api which will result in 302 (redirection) from front end so you will have to send the user directly to the serverEvaporimeter
How does that helps to figure out the providers(fb, google) response if you're simulating a href click or http redirection to another domain ?Helicon
M
2

Create a html tag to do the GET request instead, facebook does not allow XMLHttpsRequests.

Like so: <a href="http://localhost:8080/auth/facebook">sign in with facebook</a>

Marivaux answered 28/2, 2018 at 14:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.