Twitter login in Express

Hello everybody!

I’m trying to setup Twitter login on my app. I’m using Node, passport.js , Mongo I want to do something super simple:

  • Check if user exists
  • Store it in Mongo
  • Once user is logged in, display username

After 10+ tutorials I’m still struggling (I’m just starting node)

I’m not sure where to check against the database (in deserializeUser ? in the Strategy ?) Neither console.log command is executing…

Thanks for your help!

Here is my code so far:

app.use(passport.initialize());
app.use(passport.session());
app.use(cookieParser());
app.use(bodyParser());
app.use(require('express-session')({ secret: 'keyboard bobcat', resave: true, saveUninitialized: true }));

passport.use(new Strategy({
    consumerKey: CONSUMER_KEY,
    consumerSecret: CONSUMER_SECRET,
    callbackURL: "http://127.0.0.1:8080/auth/twitter/callback"
  },
    async function (token, tokenSecret, profile, done) {
        var currentUser = await db.collection('users').findAndModify({ "twitterId": profile.id},
        [['twitterId','1']],
         { "$set": { "name" : profile.name,
            "access_token_key" : token,
        "access_token_secret" : tokenSecret } },
        {"upsert":true},
        {"new":true});

        console.log("using strategy?");
        done(null, profile);
     })

    );

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

passport.deserializeUser(async function(obj, cb) {
    var currentUser = await db.collection('users').findAndModify({ "twitterId": profile.id},
        [['twitterId','1']],
         { "$set": { "name" : profile.name,
            "access_token_key" : token,
        "access_token_secret" : tokenSecret } },
        {"upsert":true},
        {"new":true});
    req.user=currentUser
    console.log("deserializing?");
    cb(null, obj);
});


app.get('/login', passport.authenticate('twitter'));


app.get('/auth/twitter/callback',
    async function (req,res) {

        try {
            passport.authenticate('twitter', { failureRedirect: '/login' })
            res.send(req.username)

        } catch(e) {
            console.log(e)
        }
});

Just a few quick things before diving any deeper:

  • Are you actually using the passport-twitter module and requiring it as follows?

    const { Strategy } = require('passport-twitter');
    
  • Do you at least get redirected to Twitter upon going to http://127.0.0.1:8080/login?

  • Have you tried console.log() as the first line inside your async function(s)? If so, does it actually log anything to the console?

  • If your async functions are actually not running, are you seeing any error messages? In case it’s a more fundamental problem—are you sure that syntax is supported in your Node environment?

2 Likes

Hi, thanks for your answer

Yes, using

var passport = require('passport');
var Strategy = require('passport-twitter').Strategy;

Yes, and authentication on Twitter is fine, I get redirected to my callback URL.

Yes, but it doesn’t log anything

The only error log I get is

body-parser deprecated bodyParser: use individual json/urlencoded middlewares server.js:96:9
body-parser deprecated undefined extended: provide extended option node_modules/body-parser/index.js:105:29

I’m not sure what it means or if it’s related.
Async are supported, I have other sync functions running fine.

Thanks for your help :slight_smile:

I see! I’m not entirely sure what’s wrong but I’m inclined to think that it’s the async function since it’s not even logging anything if there is console.log() in the first line of the block. I couldn’t, and still can’t, spot anything else that’s wrong with your code for now; to see if it’s really the async function that’s causing all the problems, perhaps try replacing the async function with a regular function first:

passport.use(
  new Strategy(
    {
      consumerKey: CONSUMER_KEY,
      consumerSecret: CONSUMER_SECRET,
      callbackURL: "http://127.0.0.1:8080/auth/twitter/callback"
    },
    function(token, tokenSecret, profile, done) {
      // Since you are getting redirected to Twitter, authorising your app should trigger
      // this function and you should see your Twitter profile ID logged to the console
      console.log(profile.id);
    }
  )
);

If you do see your profile ID logged after authorising your app on Twitter (you really should! Otherwise there is something really wrong), then try the same async again:

passport.use(
  new Strategy(
    {
      consumerKey: CONSUMER_KEY,
      consumerSecret: CONSUMER_SECRET,
      callbackURL: "http://127.0.0.1:8080/auth/twitter/callback"
    },
    async function(token, tokenSecret, profile, done) {
      console.log(profile.id);
    }
  )
);

If asyncs are working inside the passport middleware as a callback, you should also see your profile ID upon being redirected from Twitter.

No it doesn’t log anything :sob: (I litterally copied your code)

I tried commenting out my deserializer (no effect) and logging another string not relying on the profile but it still doesn’t log anything.

I don’t have time to try out your code, but I do notice the line:

app.use(bodyParser());

and the error

body-parser deprecated bodyParser: use individual json/urlencoded middlewares server.js:96:9
body-parser deprecated undefined extended: provide extended option node_modules/body-parser/index.js:105:29

I think you now need to tell it what you want to parse, for example:

app.use(bodyParser.json());

or

app.use(bodyParser.urlencoded({
  extended: true
}));

Here is an SO thread about the issue.

I’m pretty sure the problem you are having doens’t have anything to do with the body-parser error (but do fix it). I tested things on an older project earlier but decided that I should do it from scratch on Glitch, you can see my code here (I will eventually remove it and edit this post once we have worked through your problem)

When I was setting things up, I noticed that I was previously getting things logged inside the authentication callback (where your async function inside new Strategy() is) because I only modified parts of my code and things were already set up that way—it appears that the function is only called if you have the passport.authenticate('twitter') middleware in your callback route, that is:

app.get(
  "/callback",
  passport.authenticate('twitter', { failureRedirect: "/login" }), // The middleware in question
  (request, response) => {
    console.log('===========================');
    console.log('Authenticated! Redirecting!');
    console.log('===========================');
    
    response.redirect("/dreams");
  }
);

Since nothing logs for you inside the async function in your callback route, I suspect that it just simply doens’t get called at all in your case. You may want to change it into this form to see if things start working first.

It is worth noting that, if the middleware isn’t applied to the callback route, serialising doesn’t occur either. You can test this with my code by paying attention to the following lines in the console’s log after attempting login:

  • Profile ID returned by Twitter: [String]
  • SERIALISE [Object]

The lines above don’t get logged if the callback route doesn’t have the passport.authenticate('twitter') middleware.

The route /dreams is set up to be a restricted route and cannot be accessed unless the user has already logged in. Authorisation is done by the middleware that comes just before the /dreams route:

app.use((request, response, next) => {
  const { user } = (request.session || {}).passport || {};

  if (user) {
    next();
  }
  else {
    response.redirect('/');
  }
});

It checks for whether or not the request.session.passport.user exists—which is an object that is serialised into the session object after successful authentication. I’m not entirely sure if this is the proper or best way to do it—but the alternatively is to apply the passport.authenticate('twitter') instead, which will always redirect you to the callback route; in this case, if you want to redirect the user back to where she originally came from, or wanted to navigated to, you will have to have some other (relatively non-trivial) means to remember this.

Perhaps something that’s worth noting is that you re-authenticate and re-authorise the a user for any sensitive action, such as changing e-mail address and password.

I hope that helps! :slight_smile:

EDIT: Editing wording and highlighted a sentence for clarity.

1 Like

Thanks that helped a lot!