There are many questions relating to getting a req.user undefined after social authentication, but I found none which could help me.
I have been able to successfully use the example for using passport for twitter authentication: https://github.com/passport/express-4.x-twitter-example. I tried to follow this pattern as closely as possible but cannot seem to get it to work.
Specifically, I am able to successfully authenticate, but the req.user is undefined. This makes no sense to me as my user data was returned no problem from the example.
I'm not inclined to believe this is a middleware problem (as it has been for others) as the middleware is the same as that used in the example. It could be something about having multiple domains, but I'm not sure what. All of this is being done on the localhost.
In Twitter, the app is set up so that
website is: 127.0.0.1:3000/signin
and the
callback url is: 127.0.0.1:2999/auth/twitter/return
As you can tell, my client is working on port 3000 and it is making calls to a server running on port 2999.
To briefly walk you through the code, the client on 127.0.0.1:3000/signin has a button which links to 127.0.0.1:2999/auth/twitter, thus initiating the authentication request. Under the hood, the express server is created in server/index.js--server. This imports the routes in routes/index.js, some of which the controller authenticate.js handles. As you can see, the oauth twitter request is made in authenticate.js. Again, authentication proceeds successfully, I am redirected to 127.0.0.1:3000/search. However, as you can see in this.twitter_callback, I am printing the req.user and it is undefined.
Please note that I have redacted the consumer key/secret from my code.
server/index.js
var cors = require('cors')
var bodyParser = require('body-parser')
var express = require('express');
var app = express();
var http = require('http').Server(app)
var io = require('socket.io')(http)
// NOT SURE WHY I NEED TO GO BACK 3 FOLDERS TO GET TO PORT_CONFIG
var port = require("../port_config.json").server_port;
var PORT = Number(process.env.PORT || port);
var routes = require('./routes/index.js')
var database = require('./database/db.js')
var db = new database()
app.use(cors()); // middleware that allows cross-platform requests
app.use(bodyParser.json());
db.dbConnect(function(err,db_instance){
// routes
routes(app, db_instance, io)
// send user polls on connection
// TEMPORARY (WILL BE GRABBED ON LOGIN)
var user = null // WILL BE SET AFTER LOGIN
io.on('connection', function(socket) {
var places_attending = db_instance.collection('places_attending')
places_attending.find({}).toArray(function(err,docs){
var places_user_attending = docs.map(doc => {
if (doc.attending.indexOf(user) !== -1) {
return {
id: doc.id,
name: doc.name,
num_attending: doc.attending.length
}
}
})
socket.emit('places_user_attending', places_user_attending);
})
})
})
http.listen(PORT, function () {
console.log('Backend server listening at http://localhost:' + PORT);
})
module.exports = http
routes/index.js
var Search = require('../controllers/search.js')
var Add = require('../controllers/add.js')
var Authenticate = require('../controllers/authenticate.js')
module.exports = function(app, db, io) {
var search = new Search(db, io)
var add = new Add(db, io)
var authenticate = new Authenticate(app)
app.route('/api/search')
.post(search.search_yelp)
app.route('/api/add')
.post(add.add_attendee)
app.route('/auth/twitter')
.get(authenticate.twitter_authentication)
app.route('/auth/twitter/return')
.get(authenticate.twitter_callback)
}
authenticate.js
function authenticate(app) {
var passport = require('passport');
var Strategy = require('passport-twitter').Strategy;
// Configure the Twitter strategy for use by Passport.
passport.use(new Strategy({
consumerKey: REDACTED,
consumerSecret: REDACTED,
callbackURL: 'http://127.0.0.1:2999/auth/twitter/return'
},
function(token, tokenSecret, profile, cb) {
// 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.
return cb(null, profile);
}));
// Configure Passport authenticated session persistence.
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
cb(null, obj);
});
// Use application-level middleware for common functionality, including
// logging, parsing, and session handling.
app.use(require('morgan')('combined'));
app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true }));
// Initialize Passport and restore authentication state, if any, from the
// session.
app.use(passport.initialize());
app.use(passport.session());
this.twitter_authentication = passport.authenticate('twitter')
this.twitter_callback = (
passport.authenticate('twitter', { failureRedirect: 'http://127.0.0.1:3000/signin' }),
function(req, res) {
console.log('REQ.USER OBJECT: ' + req.user)
res.redirect('http://127.0.0.1:3000/search');
}
)
}
module.exports = authenticate
Any help would be greatly, greatly appreciated.
The problem was in how my twitter_callback route was specified.
If I change the callback to this:
this.twitter_callback = app.get('/auth/twitter/return',
passport.authenticate('twitter', { failureRedirect: 'http://127.0.0.1:3000/signin' }),
function(req, res) {
console.log(req.user)
res.redirect('http://127.0.0.1:3000/search');
})
everything works fine. I think this has something to do with the middleware not being applied correctly the initial way I wrote it. Not exactly sure how I would rewrite it to export it, without using app.get in the twitter_callback though
Related
I am trying to save this variable called server from the user. After they submit, they go to another page. On that page, I verify their server exists and if not send them back.
The session is saved when I run the post request because I immediately check via the get request if it exists (via client-side). However, if I refresh my page, the session is no longer defined and the user is sent back home (verified this with the logs). I think it is something cookie related, but I could not seem to get it to work.
Here is my sesssions.js route:
const session = require('express-session');
const express = require("express");
const router = express.Router();
router.use(session({
secret: 'ssshhhhh',
resave: false,
saveUninitialized: true,
cookie: { secure: false }
}));
router.get('/check', (req, res) => {
let sesh = req.session;
console.log("Checking server exists: " + sesh.server);
res.send(sesh.server);
});
router.post('/login', (req, res) => {
let sesh = req.session;
sesh.server = req.body.server;
console.log("Setting server: " + sesh.server);
res.send(sesh.server);
});
module.exports = router;
And this is how are the requests which both return promises:
function setSession(value) {
return $.post("/sessions/login", { 'server': value }).then();
}
function getSession() {
return $.get("/sessions/check").then();
}
Simple solution from a friend. You cannot instantiate middleware in a route. Just had to move the middleware instantiation to app.js.
I am very new to both JS and NodeJs/Express. I am writing a proof of concept MVP node/express app that uses passport for authentication (via social logins).
I have written the server and installed all required packages and so far everything (apart from the authentication bit works).
Note: I have already set up my credentials at the various social media company's ends, so this is not the problem.
Here is a snippet of my code:
app.js
const express = require('express');
const expressLayouts = require('express-ejs-layouts');
compression = require('compression'),
shouldCompress = (req, res) => {
if (req.headers['x-no-compression']) {
// don't compress responses if this request header is present
return false;
}
// fallback to standard compression
return compression.filter(req, res);
};
const app = express();
// EJS
app.use(expressLayouts);
app.set('view engine', 'ejs');
// Parsing related
app.use(express.urlencoded( { extended: false })); //Parse URL-encoded bodies
app.use(express.json()); //Used to parse JSON bodies
app.use(compression({
filter:shouldCompress,
threshold: 3
}));
app.use(express.static('public'));
app.disable('x-powered-by');
// Initialize Passport and restore authentication state, if any, from the session.
var passport = require('passport');
app.use(require('express-session')({ secret: 'keyboard cat', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session())
// Routes
app.use('/', require('./routes/index'));
app.use('/member', require('./routes/users'));
const PORT = process.env.PORT || 5000;
app.listen(PORT, console.log(`Server started on port: ${PORT}`));
routes/users.js
const express = require('express');
const router = express.Router();
/* GET authentication funcs */
let authentication = require('../controllers/auth.js');
router.get('/subscribe', (req, res) => { res.render('subscribe'); });
router.post('/subscribe', authentication.subscribe);
module.exports = router;
controllers/auth.js
require('dotenv').config();
const model = require("../models");
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const FacebookStrategy = require('passport-facebook').Strategy;
passport.serializeUser(function(user, done) {
done(null, user)
});
passport.deserializeUser(function(email, done) {
done(null, email)
})
function validateLead(req, done) {
mail = req.body.lead_email;
console.log('validateLead() called!');
models.Lead.findOne({
where: {
email: email
}
}).then(/* some logic */).catch();
} else {
// email already taken ..
return done(null, false, {
message: 'This email address is already subscribed'
});
}
}).catch((err) => {
console.log('An error occurred!', err);
});
}
exports.subscribe = function(req, res, next) {
switch (req.body.source) {
case 'facebook':
passport.use(new FacebookStrategy({
clientID: process.env.FACEBOOK_APP_ID,
clientSecret: process.env.FACEBOOK_APP_SECRET,
callbackURL: process.env.FACEBOOK_CALLBACK_URL
},
function(accessToken, refreshToken, fbProfile, done) {
console.log('FB callback!');
profile = {
'email': fbProfile.email,
'firstName': '',
'LastName': '',
'leadSource': '',
'tags': [],
};
return validateLead(req, done, profile);
}
));
break;
default:
console.log('Unknown');
}
}
views/test.eps
<a href='#'><i id='facebook' class='foobar'></i></a>
(simplified) views/layout.eps
$(document).ready(function(){
$('i.foobar').click(function(e){
$.ajax({
method: "POST",
url: "/member/subscribe",
data: {
"source": $(this).attr('id')
},
dataType: "json",
timeout: 5000 // 5000ms
}).done(function(data) {
// is called if request is successful
console.log(data.fridge);
}).fail(function(jqXHR, status) {
// is called if request fails or timeout is reached
alert('Request could not complete: ' + status);
});
});
});
I have put console.log() messages in controllers/auth.js and I can see that the FB code branch is being reached. However, when log messages in validateLead() and the FB callback function are not being reached.
What is the cause of this, and how do I fix it?
A couple things I can see :
As far as I could see you didn't configure passportjs. You need to have a configuration file, which for you would be the controllers/auth.js. To configure passport you need to run require('./controllers/auth')(passport); in app.js.
For passport to be able to ingest that config you need to export them as a function that takes passport e.g. module.exports = passport => {passport.use('facebook')}
Your config file (in exports.subscribe) is not a format that passport will understand. Follow the Documentation on how to create that config file.
Passport provides you with authentication middleware, I am pretty sure that you cannot create "wrappers" for them like in controllers/auth.js. To access passport's auth functions you use passport.authenticate('facebook', callback())(req, res, next) in routes/users.js.
Passport only provides middleware to serialize and deserialize users.
Your deserialization is not yet set up. You need a call to the db to fetch the user from session store.
I'm coding a session with NodeJS, when I get the user connection first create a session.client with the MAC ADDRESS, so far so good, but then I ask to the client if he want to continue and login on the app with social -network like Facebook, Instagram, Tweeter or Google+, and then when the user is redirected to the social login it back with other session from passportjs and clear al my init data of session and I lost the client information. So, I tried to change the name of the data in session, session.data, session.test, session.whatever but always happen the same, when I test and the passport redirect me and back to my domain, the session is clean and it change with new data from passportjs, any one know what's happen here? any idea how to solve this?
the code run perfectly, the problem is the session when go and back to // the social login, it clear my init data and back with the passport data. // I need my init data to continue working!
this is just an extract of code. It works
'use sctrict'
const https = require('https'),
fs = require('fs'),
path = require('path'),
morgan = require('morgan'),
logger = require('express-logger'),
express = require('express'),
favicon = require('serve-favicon'),
bodyParser = require('body-parser'),
methodOverride = require('method-override'),
passport = require('passport'),
// config files
port = 443,
mongodbConfig = require('./config/mongodb-config'),
session = require('express-session'),
keys = require('./config/keys'),
options = {
key: fs.readFileSync('./config/ssl/server.key'),
cert: fs.readFileSync('./config/ssl/server.crt')
},
cookieParser = require('cookie-parser'),
loginAPRoutes = require('./routes/loginAPRoutes'),
passportSetup = require('./config/passport-setup'),
app = express()
// MongoDB - Mongoose connection
const mongoose = require('mongoose')
mongoose.Promise - global.Promise
mongoose.connect('mongodb://' + mongodbConfig.mongodb.route + '/' + mongodbConfig.mongodb.db, {})
.then(() => console.log('db connected'))
.catch(err => console.log(err))
// config
app.set('view engine', 'ejs')
// middlewares
app.use(morgan('dev'))
app.use(favicon(path.join(__dirname, 'public/img/', 'favicon.ico')))
app.use(bodyParser.urlencoded({
extended: false
}))
app.use(bodyParser.json())
app.use(methodOverride('X-HTTP-Method-Override'))
app.use(express.static(path.join(__dirname, 'public')))
app.use(session({
secret: 'cybor-cat',
resave: false,
saveUninitialized: true
}))
// initilize passport
app.use(passport.initialize())
app.use(passport.session())
// Main routes
app.use('/guest', loginAPRoutes)
app.use('/auth', loginAPRoutes)
// Run the server https
https.createServer(options, app).listen(port, () => {
console.log('NodeJS Server Started... splice.pro is running!')
})
router.get('/s/:site', (req, res) => {
data = req.query
data.site = req.params.site
req.session.data = data
console.log('===== session ========')
console.log(req.session)
console.log('====== session END =======')
res.render('login')
})
/////////////// GOOGLE AUTH ////////////////
// route for google login
router.get('/google', passport.authenticate('google', {
scope: ['profile', 'email']
}))
// route for google and redirect
router.get('/google/callback',
passport.authenticate('google'), (req, res) => {
if (!req.user) {
res.redirect('/guest/s/site')
} else {
/////////// here comes the new session from passport :( //////
////////// and lost the first data of my session /////
console.log(req.session.data)
//////////////// this show the session with info of user ///////
/////////////// but req.session.data is lost ///////////
res.redirect('/guest/startconnection')
}
}
)
/////////////// GOOGLE AUTH END ////////////////
Well, well, well.... I found my problem, my external site redirect me to my server with the ip and when the request of passport login redirect , it back to the domain name, that's why it generate a new session id ... A long day but at the end I found it !
After setting up the Drupal as this guide says: Drupal-passport I created a simple simple node app to test how it works.
It doesn't, I get the InternalOAuthError: Failed to obtain request token error.
Going through the strategy.js, I saw that my callbackURL is logging out undefined not exactly sure why. The callbackURL is set in my Drupal app
Also preforming a curl -i -XPOST http://extranet.local/rest/system/connect/ gives me exactly what I need
Here is my node.js code (keep in mind this is just supposed to test the drupal set up).
var express = require('express');
var passport = require('passport');
var dStrategy = require('passport-drupal').DrupalStrategy;
var passportDrupal = require('passport-drupal');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser')
var session = require('express-session');
var http = require('http');
var app = express();
var server = http.createServer(app);
app.use(cookieParser());
app.use(bodyParser());
app.use(session({ secret: 'SECRET' }));
app.use(passport.initialize());
app.use(passport.session());
passport.use(new dStrategy({
consumerKey: "emDVp7P2LZFLPcN3cNCjLmrjrhQLnNv6",
consumerSecret: "mkbc3UYEuUQLNQRwLWo3B8zEk4ZrErKa",
providerURL: "http://extranet.local",
resourceEndpoint: "rest/system/connect", // <---- optional. Defaults to `rest/system/connect`
callbackURL: 'http://33.33.33.40:8888/auth/drupal/callback'
},
function(token, tokenSecret, profile, done) {
profile.oauth = { token: token, token_secret: tokenSecret };
done(null, profile);
}
));
app.get('/', function(req, res) {
res.writeHead(200);
res.end("This is root");
});
app.get('/auth/drupal',
passport.authenticate('drupal'),
function(req, res) {
// The request will be redirected to the Drupal website for
// authentication, so this function will not be called.
});
app.get('/auth/drupal/callback',
passport.authenticate('drupal', { failureRedirect: '/error' }),
function(req, res) {
// Successful authentication, redirect home.
res.redirect('/signedin');
});
app.get('/error', function(req, res) {
res.writeHead(200);
res.end("Could not sign in");
});
app.get('/signedin', function(req, res) {
res.writeHead(200);
res.end("signed in");
});
server.listen(8888, '33.33.33.40');
Any clues as to why or ideas are greatly appreciated
If you look into the strategy.js code of the library passport-drupal, you will see that the DrupalStrategy constructor does not expect a callbackURL property in the options parameter object and it also does not pass it further into the OAuthStrategy.
This is the code snippet that creates the parameter for the oauth strategy:
// Determine all necessary OAuth options
var oauthOptions = {
requestTokenURL: this._providerURL + '/oauth/request_token',
accessTokenURL: this._providerURL + '/oauth/access_token',
userAuthorizationURL: this._providerURL + '/oauth/authorize',
consumerKey: options.consumerKey,
consumerSecret: options.consumerSecret
};
OAuthStrategy.call(this, oauthOptions, verify);
It should be modified to pass the callbackURL, for example like this:
// Determine all necessary OAuth options
var oauthOptions = {
requestTokenURL: this._providerURL + '/oauth/request_token',
accessTokenURL: this._providerURL + '/oauth/access_token',
userAuthorizationURL: this._providerURL + '/oauth/authorize',
consumerKey: options.consumerKey,
consumerSecret: options.consumerSecret,
callbackURL: options.callbackURL// <==== THIS LINE WAS ADDED
};
OAuthStrategy.call(this, oauthOptions, verify);
I'm not sure that will solve your issue though. But I made a pull request
Am working on a project using the mean stack.
Before setting up the client side am testing my routes using postman.
http://www.getpostman.com/
Am trying to send a post request to fetch a specific user and a post request to add a user.
This is the code....
//cons.js
'use strict';
var strg = 'mongodb://localhost/users';
module.exports={
strg:strg
}
//models.js
'use strict';
var mong = require('mongoose'),
str = require('./cons');
mong.connect(str.strg);
var pips = mong.model('pips',{
name: {type: String, required: true, index: { unique: true }},
pass: {type: String, required: true},
act:{type: Boolean}
});
module.exports = {
pips: pips
}
//user_ctrl.js
var addPips, getPipsById,
pip = require('./models').pips;
getPipsById = function(req, res){
/*pip.findOne({jina:req.params.unam}).exec(
function(err,rez){
if(err||!rez){
res.send(err);
}
res.json(rez);
})*/
pip.findOne({jina:req.body.unam}).exec(
function(err,rez){
if(err||!rez){
res.send(err);
}
res.json(rez);
})
};
addPips = function(req, res){
pip.create({
name: req.body.unam,
pass: req.body.upas,
act: false
}, function(err, rez){
if(err||!rez){
res.send(err);
}
res.send("User account created...");
})
};
module.exports = {
addPips: addPips,
getPipsById : getPipsById
}
//routes.js
'use strict';
var jada = require('./user_ctrl');
module.exports = function(app){
//app.get('/api/users/:unm',jada.getUserById);
app.post('/api/users',jada.getPipsById);
app.post('/api/users',jada.addPips);
app.all('/api/*', function(req, res) {
res.send(404);
});
};
//server.js
'use strict';
var express = require('express'),
morgan = require('morgan'),
port =process.env.PORT || 3000,
bodyParser = require('body-parser'),
methodOverride = require('method-override'),
app = express();
app.use(morgan('dev'));
app.use(bodyParser());
app.use(methodOverride());
app.use(express.static(__dirname+"/app"));
require('./config/models');
require('./config/user_ctrl');
require('./config/routes')(app);
app.listen(port);
console.log('Magic happens on port: '+port);
Am using a post request to fetch a specific user because I am picking information from the form and not the url.
When am adding the user. I get an error message: 'validation error name and pass required'. I don't seem to see where this is coming from because I am assigning values from the form to the parameters.
Can anyone tell me what am doing wrong? Thank you...
Well, it turns out that Express Body-Parser does not know how to deal with data when the content-type is not set.
I found out from here:
Express.js req.body undefined
I use postman to test my routes before building the client side. In postman, when simulating form posts I used x-www-form-urlencoded it sets the content-type automatically.
If you are getting undefined form values, try checking if your headers (content-type) are properly set.