Twitter authentication with Passport middleware in Node

6.6k views Asked by At

I'm developing a website with Node.js (using Express framework). In order to use Twitter authentication, I'm using passport module (http://passportjs.org), and his wrapper for Twitter called passport-twitter.

My server-side script is:

/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
  , user = require('./routes/user')
  , http = require('http')
  , path = require('path')
  , passport = require('passport')
  , keys = require('./oauth/keys')
  , TwitterStrategy = require("passport-twitter").Strategy;

var app = express();

app.configure(function(){
  app.set('port', process.env.PORT || 3000);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.favicon());
  app.use(express.logger('dev'));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(express.cookieParser('foo'));
  app.use(express.session());
  // Initialize Passport!  Also use passport.session() middleware, to support
  // persistent login sessions (recommended).
  app.use(passport.initialize());
  app.use(passport.session());
  app.use(app.router);
  app.use(require('less-middleware')({ src: __dirname + '/public' }));
  app.use(express.static(path.join(__dirname, 'public')));
});

app.configure('development', function(){
  app.use(express.errorHandler());
});

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function (err, user) {
    done(err, user);
  });
});

passport.use(new TwitterStrategy({
    consumerKey: keys.twitterConsumerKey,
    consumerSecret: keys.twitterConsumerSecret,
    callbackURL: "http://local.host:3000/auth/twitter/callback"
  },
  function(token, tokenSecret, profile, done) {
    User.findOrCreate({ twitterId: profile.id }, function (err, user) {
      if (err) { return done(err); }
      else { return done(null, user); }
    });
  }
));

app.get('/', routes.index);
app.get('/contacts', routes.contacts);
app.get('/cv', routes.cv);
app.get('/projects', routes.projects);
app.get('/users', user.list);

// Redirect the user to Twitter for authentication.
// When complete, Twitter will redirect the user back to the
// application at /auth/twitter/callback
app.get('/auth/twitter', passport.authenticate('twitter'));

// Twitter will redirect the user to this URL after approval.  Finish the
// authentication process by attempting to obtain an access token.  If
// access was granted, the user will be logged in.  Otherwise,
// authentication has failed.
app.get('/auth/twitter/callback', 
  passport.authenticate('twitter',
    {
      successRedirect: '/',
      failureRedirect: '/login'
    }
  )
);

http.createServer(app).listen(app.get('port'), function(){
  console.log("Express server listening on port " + app.get('port'));
});

The URI associated to login is http://local.host:3000/auth/twitter; when I visit it, Twitter shows me the authentication form for linking my account with my own website but, after this step, the following error occurs:

Express
500 ReferenceError: User is not defined

How can I solve this problem? Best regards, Vi.

5

There are 5 answers

3
Max On BEST ANSWER

You have to define your User type somewhere. It looks like you expect this thing User to exist and to have the functions findOrCreate and findById, but you never defined that anywhere. Where are you 'finding' these users? The ones that aren't found, where are they being 'created'? Are you using a database? How do you connect to the database? I think you forgot the "Model" step. You might want to take a look at Mongoose Auth which is like Passport but it plugs directly into Mongoose, which connnects to a Mongo Database

4
vsync On

This is what I did when I faced the same error which says User isn't defined:

passport.use(new TwitterStrategy({
    consumerKey: keys.twitterConsumerKey,
    consumerSecret: keys.twitterConsumerSecret,
    callbackURL: "http://local.host:3000/auth/twitter/callback"
  },
  function(token, tokenSecret, profile, done) {
    done(null, profile);
  }
));
0
eskot On

I ran into the same issue when integrating the BeatsMusic OAuth2 strategy for Passport within Kraken. It looks like the examples for the various Kraken Passport integration strategies use the same simple example documentation which did not explicitly discuss the User object (understandable).

I figured out (from digging thru the passport strategy examples found @ https://github.com/krakenjs/kraken-examples/tree/master/with.passport) that the User is intended to be a model based on the Mongoose model schema and also is configured with the https://github.com/drudge/mongoose-findorcreate plugin.

After i included the User = require('../PATH_TO/user') and added this plugin to the User model, voila! no more errors :)

Sounds like you don't need the DB functionality so you are probably good with removing the auth check.

Hope this helps for anyone else having similar issues.

0
Jeff Anderson On

To further explain on Max's answer: "You need to Create User yourself"

Read Here

TL:DR - Basically you have to have a userschema that you use to set users and verify users, it needs a mongoose db backend, which is actually simple to configure.

essentially creating this middleware:

var mongoose = require('mongoose');
var bcrypt   = require('bcrypt-nodejs');

// define the schema for our user model
var userSchema = mongoose.Schema({

    local            : {
        email        : String,
        password     : String,
        group        : String,
    },
    facebook         : {
        id           : String,
        token        : String,
        email        : String,
        name         : String
    },
    twitter          : {
        id           : String,
        token        : String,
        displayName  : String,
        username     : String
    },
    google           : {
        id           : String,
        token        : String,
        email        : String,
        name         : String
    }

});

// methods ======================
// generating a hash
userSchema.methods.generateHash = function(password) {
    return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};

// checking if password is valid
userSchema.methods.validPassword = function(password) {
    return bcrypt.compareSync(password, this.local.password);
};

// create the model for users and expose it to our app
module.exports = mongoose.model('User', userSchema);
0
Enzo On

I think the api is not ready for the cases not need User's db integration. My solution was ignore the done() function and make a redirect to the success page.

passport.use(new TwitterStrategy({
    consumerKey: keys.twitterConsumerKey,
    consumerSecret: keys.twitterConsumerSecret,
    callbackURL: "http://local.host:3000/auth/twitter/callback"
  },
  function(token, tokenSecret, profile, done) {
    //done(null, profile);
    this.redirect('/auth/success');
  }
));