Search code examples
node.jsvue.jspassport.jsaxiospassport-twitter

Node.js/Passport Twitter authorization returns 302


I'm doing a POST request to authorize Twitter via Passport Twitter strategy. The request is going on fine if I use Postman but it fails with if I use it in my app. I get a 302 in response and this in browser console

Error: Network Error
Stack trace:
createError@webpack-internal:///99:16:15
handleError@webpack-internal:///96:87:14

From what I read on the net I found that 302 would be because of some bad implementation of the a redirect call? Maybe is it that twitter does when authorizing a application and I'm not handling it correctly?

The code is as follows. Client-side using Vuejs with Axios to send a request.

Vue template:

<template>
  <card>
    <h4 slot="header" class="card-title">Edit linked accounts</h4>
    <form>
      <button type="submit" class="btn btn-info btn-fill" @click.prevent="authTwitter">
          Link Facebook
        </button>
      <div class="clearfix"></div>
    </form>
  </card>
</template>
<script>
  import Card from 'src/components/UIComponents/Cards/Card.vue'
  import controller from '../../../../../src/controller/AuthController'

  export default {
    components: {
      Card
    },
    data () {
      return {
      }
    },
    methods: {
      authTwitter () {
             let token = this.$session.get('jwt')

  controller.http.post('/auth/twitter',{}, {
      headers: {
        Authorization: 'Bearer ' + token
      }
    })
    .then(function(response) {
      console.log(response)
    })
    .catch(function(error) {
      console.log(error);
    })

      }
    }
  }

</script>
<style>

</style>

Axios settings:

const axios = require('axios');
const config = require('../../config/index.js')

let http = axios.create({
    baseURL: config.url,
    'Content-Type' : 'application/x-www-form-urlencoded',
    'Access-Control-Allow-Origin':'*'
})

module.exports = {
  http
}

And in my backend I'm doing the following:

Passport settings:

const passport = require('passport');
const StrategyTwitter = require('passport-twitter').Strategy,
  LocalStrategy = require('passport-local').Strategy;
const JwtStrategy = require('passport-jwt').Strategy,
  ExtractJwt = require('passport-jwt').ExtractJwt;

const {
  User,
  UserDetails,
  UserAccounts
} = require('../models');

const bcrypt = require('bcrypt-nodejs');
const jwt = require('jsonwebtoken');
const config = require('../config/config');
let session = require('express-session')
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = config.secretKey;


module.exports = function(app) {

  // Local sign-in stragetgy
  passport.use('local-signin', new LocalStrategy(
    function(username, password, done) {
      User.findOne({
        where: {
          username: username
        }
      }).then(function(data) {
        bcrypt.compare(password, data.password, function(err, response) {
          if (err) {
            return done(err);
          }
          const token = jwt.sign({
            id: data.id
          }, config.secretKey, {
            expiresIn: 86400 // 86400 expires in 24 hours
          });
          return done(null, token);
        });
      }).catch(function(error) {
        return done(error)
      });
    }
  ));


  // Local sign-up strategy
  passport.use('local-signup', new LocalStrategy({
      usernameField: 'username',
      passwordField: 'password',
      passReqToCallback: true // allows us to pass back the entire request to the callback
    },
    function(req, username, password, done) {
      process.nextTick(function() {

        User.beforeCreate(function(req) {
          return encryptPass(req.password)
            .then(success => {
              req.password = success;
            })
            .catch(err => {
              if (err) console.log(err);
            });
        });

        User.create({
          username: username,
          password: password
        }).then(function(data) {
          UserDetails.create({
            username: req.body.username,
            name: req.body.name,
            dob: req.body.dob,
            phone: req.body.phone,
            gender: req.body.gender,
            address: req.body.address,
            country: req.body.country,
          }).then(function(data) {
            UserAccounts.create({
              username: username
            }).then(function(data) {
              return done(null, data);
            }).catch(function(error) {
              return done(error.message);
            });
          }).catch(function(error) {
            return done(error.message);
          });
        }).catch(function(error) {
          return done(error.message);
        });
      })
    }
  ));

  // Passport Jwt strategy
  passport.use('jwt', new JwtStrategy(opts, function(jwt_payload, done) {
    console.log('jwt', jwt_payload);
    UserDetails.findOne({
        where: {
          id: jwt_payload.id
        }
      })
      .then(function(user) {
        return done(null, user.id);
      })
      .catch(function(err) {
        return done(err, false);
      });
  }));


  // Use sessions for twitterStrategy Oauth1 authorizations.
  passport.serializeUser(function(user, cb) {
    console.log('user', user)
    cb(null, user);
  });

  passport.deserializeUser(function(obj, cb) {
    console.log('obj', obj)
    cb(null, obj);
  });

  // Configure the Twitter strategy for use by Passport.
  //
  // OAuth 1.0-based strategies require a `verify` function which receives the
  // credentials (`token` and `tokenSecret`) for accessing the Twitter API on the
  // user's behalf, along with the user's profile.  The function must invoke `cb`
  // with a user object, which will be set at `req.user` in route handlers after
  // authentication.
  passport.use('twitter-authz', new StrategyTwitter({
      consumerKey: config.keys.twitter.consumerKey,
      consumerSecret: config.keys.twitter.consumerSecret,
      callbackURL: process.env.CALLBACK_URL_TWITTER || 'http://120.0.0.1:8000/#/dashboard/user'
    },
    function(token, tokenSecret, profile, cb) {
       process.nextTick(function() {
        // In this example, the user's Twitter profile is supplied as the user
        // record.  In a production-quality application, the Twitter profile should
        // be associated with a user record in the application's database, which
        // allows for account linking and authentication with other identity
        // providers.
        console.log(token, tokenSecret, profile);
        return cb(null, profile);
      })
    }));


  // Initialize Passport and restore authentication state, if any, from the
  // session.
  app.use(passport.initialize());

  // Session for twitterStrategy
  app.use(session({
    secret: config.secretKey,
    resave: false,
    saveUninitialized: false
  }));

}

function encryptPass(pass) {
  return new Promise((resolve, reject) => {
    bcrypt.hash(pass, null, null, function(err, hash) {
      if (err) {
        return reject(err);
      };
      return resolve(hash);
    })
  })
}

And my router settings:

const AuthControllerPolicy = require('./controllers/policies/AuthControllerPolicy.js')
const AuthController = require('./controllers/AuthController.js')

const passport = require('passport');

module.exports = (app) => {

    app.post('/auth/twitter',
      passport.authenticate('jwt', { session: false }),
      passport.authorize('twitter-authz', { session: false }),
      AuthController.authTwitter)
}

Solution

  • Answer found here: Axios and twitter API

    Looks like Twitter would not allow CORS request and it must be done only from the server-side.