getting CORS error while using passport-facebook for facebook login in MEAN app

2k views Asked by At

I am trying to add facebook login feature for my web app. Which is being developed in express js, mongodb and angular 2.

Client part of the app is generated through angular-cli (which is running on port 4200). To connect it to the express app (which is running on port 4300) I am using proxy config provided by angular-cli itself.

I have searched web and nothing is working for me. Please help me out.

This is the Error I am getting while clicking on facebook login button

 XMLHttpRequest cannot load https://www.facebook.com/dialog/oauth?response_type=code&redirect_uri=http%…alhost%3A4200%2Fapi%2Fuser%2Ffacebook%2Fcallback&client_id=404659989876073. Redirect from 'https://www.facebook.com/dialog/oauth?response_type=code&redirect_uri=http%…alhost%3A4200%2Fapi%2Fuser%2Ffacebook%2Fcallback&client_id=404659989876073' to 'https://www.facebook.com/login.php?skip_api_login=1&api_key=404659989876073…_&display=page&locale=en_GB&logger_id=9f59d18b-00cb-48a9-a544-7e49a66acfe4' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access.

I can understand that this is CORS issue, I have searched a lot of place but all the solutions which are available online are not working for me. Like allowing cross origin in express, using third party library for it etc.

Here is my app.ts

import * as express from 'express';
import { json, urlencoded } from 'body-parser';
import * as path from 'path';
import * as cors from 'cors';
import * as compression from 'compression';
import * as mongoose from 'mongoose';
import * as ejs from 'ejs';
import * as passport from 'passport';
import * as session from 'express-session';

import { MONGODB_URI } from './config';
import { AuthConfig } from './api/auth/passport';

import { thingRouter } from './api/thing/';
import { userRouter } from './api/user';

const app: express.Application = express();


app.disable('x-powered-by');

app.use(json());
app.use(compression());
app.use(urlencoded({ extended: true }));
app.use(session({ secret: 'my-secret-key', resave: true, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());

mongoose.connect(process.env.MONGODB_URI || MONGODB_URI);

// allow cors only for local dev

app.use(cors({
  origin: 'http://localhost:4200'
}));


AuthConfig(passport);
// app.set('env', 'production');

// api routes

app.use('/api/thing', thingRouter);
app.use('/api/user', userRouter);

if (app.get('env') === 'production') {

  // in production mode run application from dist folder
  app.use(express.static(path.join(__dirname, '/../client')));
  app.engine('html', ejs.renderFile);
  app.set('view engin', 'html');
}

// catch 404 and forward to error handler
app.use(function (req: express.Request, res: express.Response, next) {
  res.render(path.join(__dirname, '/../client/index.html'));
});

// production error handler
// no stacktrace leaked to user
app.use(function (err: any, req: express.Request, res: express.Response, next: express.NextFunction) {

  res.status(err.status || 500);
  res.json({
    error: {},
    message: err.message
  });
});

export { app }

passport.ts

let LocalStrategy = require('passport-local').Strategy;
let FacebookStrategy = require('passport-facebook').Strategy;

import { FacebookAuth } from '../../config';

import { User } from '../user/user.model'

import * as uuid from 'uuid';

 let passportConfig = function (passport) {
  passport.serializeUser((user, done) => {
    console.log('serializeing user');
    done(null, user.id);
  });

  passport.deserializeUser((id, done) => {
    console.log('deserializing user');
    User.findOne({ id: id }, (err, user) => {
      done(err, user);
    })
  });


  // Facebook Strategy
  passport.use(new FacebookStrategy({
    clientID: FacebookAuth.clientId,
    clientSecret: FacebookAuth.clientSecret,
    callbackURL: FacebookAuth.callbackURL // for my app - 'http://localhost:4200/api/user/facebook/callback'
  },
  (token, refreshToken, profile, done) => {
    console.log('in passport.js'); //never printed
    console.log('token', token); //never printed
    console.log('refreshToken', refreshToken); //never printed
    console.log('profile', profile); // never printed
    User.findOne({ 'facebook.id': profile.id}, (err, user) => {
      if (err) { return done(err) }
      if (user) { return done(null, user) }
      let newUser = new User();
      newUser.id = uuid.v4();
      newUser.facebook.id = profile.id;
      newUser.facebook.token = token;
      newUser.name = profile.name.givenName + ' ' + profile.name.familyName;
      newUser.email = profile.emails[0].value;
      newUser.save(err => {
        if (err) { return done(err) }
        return done(null, newUser);
      })
    })
  }));
}

export let AuthConfig = passportConfig;

route file

import { Router } from 'express';

import { UserController } from './user.controller';

const userRouter: Router = Router();
const controller: UserController = new UserController();

userRouter.get('/facebook', controller.facebook);
userRouter.get('/facebook/callback', controller.facebookCallback);


export { userRouter };

User controller file

import { Router, Response, Request, NextFunction } from 'express';
import * as passport from 'passport';
import * as uuid from 'uuid';

import { User } from './user.model';

export class UserController {
  constructor() { }

  facebook(req: Request, res: Response, next: NextFunction) {
    console.log('got the request in facebook()'); //this get printed
    passport.authenticate('facebook')(req, res, next);

  }

  facebookCallback(req: Request, res: Response, next: NextFunction) {
    console.log('got the call in facebookCallback()');
    passport.authenticate('facebook', (err, user) => {
      console.log('passport callback\'s callback');
      if (err) {
        return res.status(200)
        .json({
          title: 'error',
          data: err
        })
      }
      return res.status(200)
        .json({
          title: 'logged in',
          data: user
        })
    })
  }
}

**HTML for facebook icon with click event **

<div class="col-sm-2 col-sm-offset-4 fb" (click)="facebookLogin()">
        <i class="fa fa-facebook-square"></i>
 </div>

Angular 2 component (function) which is working fine

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { ToastyService, ToastyConfig, ToastOptions, ToastData } from 'ng2-toasty';

import { AuthService } from './../../services/auth/auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.less']
})
export class LoginComponent implements OnInit {

  constructor(private toastyService: ToastyService,
    private router: Router,
    private toastyConfig: ToastyConfig,
    private authService: AuthService) {

    this.toastyConfig.theme = 'bootstrap';

  }

  ngOnInit() {
      }

  facebookLogin() {
    this.authService.facebookLogin()
    .subscribe(
      data => {
        console.log('login.component data', data);
      },
      err => {
        console.log('login.component err', err);
      }
    )
  }

}

Auth Service

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs';
import 'rxjs/Rx';


@Injectable()
export class AuthService {

  constructor(private http: Http) {
  }

  facebookLogin() {
    return this.http.get('/api/user/facebook')
    .map(response => response.json())
    .catch(err => Observable.throw(err.json));
  }



}

3

There are 3 answers

0
sachin yadav On

Try using Link like http://localhost:port/api/users/facebook and set header import header and set header.append('Content-Type','application/json');

1
amrendra On

This is what I have done to avoid Angular to call facebook auth dialogue API as AJAX request.

Use 'window.location="http://localhost:3000/auth/facebook"'; in your Angular Controller Function from where you send the request to your Express Server which contains passport.authenticate stuff.

It works for me!

0
AlexNasonov On

I have exactly the same problem. I tried to solve it the similar way amey discribed, but it ends with express creating two different sessions: one for authed client and one for other calls from frontside app.