I am not seeing any effect on the req.user or req.session.passport.user objects as a result of deserializeUser().
My understanding is that successful completion of deserializeUser() should result in my User object which I retrieve from a DB should be set to the req.user property. Is this correct?
Furthermore, it seems that the purpose of this callback is to allow me to serialize a small object (e.g.: {username: 'me', email: 'me#me.com'}) in the cookie itself, but then add more user info retrieved from the database to use downstream.
Instead, I see the following:
passport.serializeUser(function(user, done) {
// user: 'stan#stadelman.com'
fetchUser(user, function(u) {
// u: { email: 'stan#stadelman.com', name: 'Stan Stadelman' }
done(null, u);
});
});
passport.deserializeUser(function(id, done) {
// id: { email: 'stan#stadelman.com', name: 'Stan Stadelman' }
fetchUser(id.email, function(user) {
// user: { email: 'stan#stadelman.com', name: 'Stan Stadelman', local_props: 'from_db'}
done(null, user);
});
});
req.user = 'stan#stadelman.com'
req.session = { cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
passport: { user: { email: 'stan#stadelman.com', name: 'Stan Stadelman' } } }
My expectation was that the req.session.passport.user and/or req.user objects should contain the local_props property value. Am I reading the documentation & tutorials incorrectly, or is there an issue? I've cross-posted in passport.js github here.
My express & passport setup is as follows:
// express configs
app.use(cookieParser(expressSecret));
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(expressSession({
secret: expressSecret,
resave: true,
saveUninitialized: true }
));
app.use(passport.initialize());
app.use(passport.session());
// passport implementation
passport.use(new BasicStrategy(/...));
You should pass user id to done callback in serialize
passport.serializeUser(function(user, done) {
done(null, user.id);
});
And then use that id to fetch user in deserialize
passport.deserializeUser(function(id, done) {
fetchUser(id, function(user) {
done(null, user);
});
});
I run into a similar problem but caused by something else. Passport.js deserializeUser() does its job setting user object to req.user:
passport.deserializeUser(async function(id, done) {
try {
const account = await Account.findById(id);
done(null, account);
} catch (error) {
done(error, null)
}
});
However, in order to check whether a user is logged in, I have a route on the server /account/me that takes req.user and returns it as json:
router.route('/account/me').get(mwAuthentication, (req, res) => {
res.json(req.user);
});
To filter sensitive properties, I have a method toJSON() defined in Mongoose schema:
AccountSchema.methods.toJSON = function() {
let account = this;
let accountObject = account.toObject();
return _.pick(accountObject, ['_id', 'fullName', 'email', 'facebookId', 'photoUrl']);
};
When you call res.json(req.user) it calls JSON.stringify(req.user) internally, which again calls my toJSON() method and limits the object to properties that I picked.
Hope this helps someone.
Related
First of all, please understand that I am not good at English.
I am currently implementing authentication using NextJS (full framework) + passport.
I succeeded in getting the user's Twitter information using strategy, and after that, I'm going to jwt the user's information in db and deliver it to you as a cookie, but I don't know when and where this should be done.
code is like
lib/passport.ts
passport.use(
new TwitterStrategy(
{
consumerKey: process.env.TWITTER_CONSUMER_KEY as string,
consumerSecret: process.env.TWITTER_CONSUMER_SECRET as string,
callbackURL: '/api/auth/callback/twitter',
includeEmail: true,
},
async (_accessToken, _refreshToken, profile: TwitterProfile, cb: any) => {
try {
return cb(null, profile);
} catch (e: any) {
throw new Error(e);
}
}
)
);
req.session.passport
passport.serializeUser((user, cb) => {
process.nextTick(function () {
return cb(null, user);
});
});
passport.deserializeUser(function (
user: any,
cb: (arg0: null, arg1: any) => any
) {
process.nextTick(function () {
return cb(null, user);
});
});
middleware/auth.ts
const auth = nextConnect()
.use(
session({
secret: process.env.NEXTAUTH_SECRET,
cookie: {
maxAge: 60 * 60 * 8, // 8 hours,
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
},
resave: false,
saveUninitialized: true,
})
)
.use((req, res, next) => {
// Initialize mocked database
// Remove this after you add your own database
req.session.users = req.session.users || [];
next();
});
api/auth/callback/twitter.ts
import nextConnect from 'next-connect';
import passport from '#/lib/passport-twitter';
import auth from '#/middleware/auth';
const handler = nextConnect();
handler
.use(auth)
.use(passport.session())
.get((req, res) => {
passport.authenticate('twitter', async () => {
// I tried to set header here
res.setHeader('Set-Cookie', 'test');
res.end();
});
});
And when I try to log in additionally, where can I check the client request's cookies? (for access_token verification).
Lack of knowledge may lead to a lack of explanation and a wrong approach.
I want to use authentication information to store user information according to the situation. For example, if you have another account stored in the same email (Facebook), you can send a specific message without creating a user.
I am working to implement passport.js in my react/node/express/sequelize app.
I currently have middleware working for logging a user in, and checking if the user is authenticated. However, when a new user signs up or registers, their user data is not being saved to the server session (even though it is created in the DB). This means after a user registers, they have to go to the login page, enter their credentials and hit login before their session is saved.
My login function is simple:
router.post('/login', passport.authenticate('local'), (req, res) => {
//console.log(req);
console.log("Is authenticated: " + req.isAuthenticated());
res.json(req.user);
});
it uses passport.authenticate local strategy, which I've defined as:
passport.use(new LocalStrategy(
{
usernameField: 'email',
},
((email, password, done) => {
User.findOne({
where: {
email,
},
}).then((dbUser) => {
if (!dbUser) {
return done(null, false, {
message: 'Incorrect email.',
});
}
if (!dbUser.validPassword(password)) {
return done(null, false, {
message: 'Incorrect password.',
});
}
return done(null, dbUser);
});
}),
));
I know from the passport documentation and from looking at other questions that the passport.authenticate local strategy automatically calls the req.login() function, which serializes my user information and saves it in the server session.
My main issue is I'm not sure exactly how to implement this during my register function.
router.post('/signup', (req, res) => {
const user = {
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
password: req.body.password,
};
User.findOrCreate({where: {email: user.email}, defaults: user})
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the User."
});
});
});
I've tried calling req.login() after findOrCreate, but I get an error:
(node:42313) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
If I use my local strategy, I get an unauthorized response (since the credentials I'm using to authorize are not yet in the DB).
I figure I need to make a custom strategy for sign in, but it's not clear to me if that's the right approach, or how I would specify it.
I fixed this by adding req.login before res.send:
router.post('/signup', (req, res) => {
const user = {
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
password: req.body.password,
};
User.findOrCreate({where: {email: user.email}, defaults: user})
.then(data => {
req.login(data[0], function(err) {
if (err) {
console.log("login function erroring out with: " + err)
}
});
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the User."
});
});
});
I know this question was asked many times in stack over flow. I tried every accepted answers but can't my local strategy into function. Here is my code
var express = require('express');
var app = express();
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
app.use(cookieParser()); // read cookies (needed for auth)
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.set('trust proxy', 1); // trust first proxy
app.use(session({
secret: '564sdf4as564f56a7s765s4afjkgadxjkbadksj',
resave: true,
saveUninitialized: true,
cookie: { secure: true }
}));
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy({
usernameField:'userName',
passwordField:'password',
passReqToCallback : true
},function(request, userName, password, done) {
console.log(request);
UserAccount.findOne({'userName': userName} , function(err, user) {
if (err) return done(err);
if (!user) return done(null, false, 'Incorrect username.' );
user.verifyPassword(password, function(err, isMatch) {
if (isMatch) {
return done(null, user);
} else {
return done(null, false, 'Incorrect password.');
}
});
});
}));
passport.serializeUser(function(user, done) {
console.log('Serialize user called');
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log('Deserialize user called');
UserAccount.findById(id, function(err, user) {
done(err, user);
});
});
Then I created a router like
var router = express.Router();
require('./controllers/user')(router,passport);
app.use('/api',router);
Then in my user controller I created signIn function like
app.post('/signIn',function (request,response,next){
var variables = request.body;
console.log(variables);
passport.authenticate('local', function(error, user, info) {
console.log(user);
if (error) { console.log(error); return next(err); }
if (!user) { return response.redirect('/login'); }
response.logIn(user, function(err) {
if (err) { return next(err); }
return response.redirect('/users/' + user.username);
});
})(request, response, next);
});
Then I send a request from "Postman"
{
"userName":"karthik#abc.com",
"password":"qwerty"
}
My mongodb userName and password fields are same.
In my db there is an account with this user name and password. But every time it return 'user' as 'false' inside authenticate. I tried to console my request inside local strategy but it never gets called. I don't understand What I done wrong here? Can some one help to solve this? Thank you very much.
You should name local strategy and use it in authenticate.
Use like this passport.use('local-strategy',new LocalStrategy({});
and like passport.authenticate('local-strategy');
I'm trying to build authentication system with ExpressJS and PassportJS. For session store I use Redis. I wanna use Remember Me. Every time when the user signs in and has marked "remember me" check-box, it should automatically sign in by next visit on site. I have downloaded an example app form Github https://github.com/jaredhanson/passport-remember-me and change for my using.
var express = require('express')
, passport = require('passport')
, LocalStrategy = require('passport-local').Strategy
, mongodb = require('mongodb')
, mongoose = require('mongoose')
, bcrypt = require('bcrypt')
, SALT_WORK_FACTOR = 10
, RedisStore = require('connect-redis')(express);
mongoose.connect('localhost', 'test');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function callback() {
console.log('Connected to DB');
});
// User Schema
var userSchema = mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true},
accessToken: { type: String } // Used for Remember Me
});
// Bcrypt middleware
userSchema.pre('save', function(next) {
var user = this;
if(!user.isModified('password')) return next();
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if(err) return next(err);
bcrypt.hash(user.password, salt, function(err, hash) {
if(err) return next(err);
user.password = hash;
next();
});
});
});
// Password verification
userSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if(err) return cb(err);
cb(null, isMatch);
});
};
// Remember Me implementation helper method
userSchema.methods.generateRandomToken = function () {
var user = this,
chars = "_!abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
token = new Date().getTime() + '_';
for ( var x = 0; x < 16; x++ ) {
var i = Math.floor( Math.random() * 62 );
token += chars.charAt( i );
}
return token;
};
// Seed a user
var User = mongoose.model('User', userSchema);
var usr = new User({ username: 'bob', email: 'bob#example.com', password: 'secret' });
usr.save(function(err) {
if(err) {
console.log(err);
} else {
console.log('user: ' + usr.username + " saved.");
}
});
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing.
//
// Both serializer and deserializer edited for Remember Me functionality
passport.serializeUser(function(user, done) {
var createAccessToken = function () {
var token = user.generateRandomToken();
User.findOne( { accessToken: token }, function (err, existingUser) {
if (err) { return done( err ); }
if (existingUser) {
createAccessToken(); // Run the function again - the token has to be unique!
} else {
user.set('accessToken', token);
user.save( function (err) {
if (err) return done(err);
return done(null, user.get('accessToken'));
})
}
});
};
if ( user._id ) {
createAccessToken();
}
});
passport.deserializeUser(function(token, done) {
User.findOne( {accessToken: token } , function (err, user) {
done(err, user);
});
});
// Use the LocalStrategy within Passport.
// Strategies in passport require a `verify` function, which accept
// credentials (in this case, a username and password), and invoke a callback
// with a user object. In the real world, this would query a database;
// however, in this example we are using a baked-in set of users.
passport.use(new LocalStrategy(function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
user.comparePassword(password, function(err, isMatch) {
if (err) return done(err);
if(isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Invalid password' });
}
});
});
}));
var app = express();
// configure Express
app.configure(function() {
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.engine('ejs', require('ejs-locals'));
app.use(express.logger());
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.session({
store: new RedisStore({ host: '127.0.0.1', port: 6379, prefix: 'chs-sess' }),
secret: '4Md97L1bL4r42SPn7076j1FwZvAiqube',
maxAge: new Date(Date.now() + 3600000)
}));
// Remember Me middleware
app.use( function (req, res, next) {
if ( req.method == 'POST' && req.url == '/login' ) {
if ( req.body.rememberme ) {
req.session.cookie.maxAge = 2592000000; // 30*24*60*60*1000 Rememeber 'me' for 30 days
} else {
req.session.cookie.expires = false;
}
}
next();
});
// 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(express.static(__dirname + '/../../public'));
});
app.get('/', function(req, res){
res.render('index', { user: req.user });
});
app.get('/account', ensureAuthenticated, function(req, res){
res.render('account', { user: req.user });
});
app.get('/login', function(req, res){
res.render('login', { user: req.user, message: req.session.messages });
});
// POST /login
// Use passport.authenticate() as route middleware to authenticate the
// request. If authentication fails, the user will be redirected back to the
// login page. Otherwise, the primary route function function will be called,
// which, in this example, will redirect the user to the home page.
//
// curl -v -d "username=bob&password=secret" http://127.0.0.1:3000/login
//
/***** This version has a problem with flash messages
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login', failureFlash: true }),
function(req, res) {
res.redirect('/');
});
*/
// POST /login
// This is an alternative implementation that uses a custom callback to
// acheive the same functionality.
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err) }
if (!user) {
req.session.messages = [info.message];
return res.redirect('/login')
}
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/');
});
})(req, res, next);
});
app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
app.listen(3000, function() {
console.log('Express server listening on port 3000');
});
// Simple route middleware to ensure user is authenticated.
// Use this route middleware on any resource that needs to be protected. If
// the request is authenticated (typically via a persistent login session),
// the request will proceed. Otherwise, the user will be redirected to the
// login page.
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/login')
}
My app doesn't work with Remember Me, every time when I close the browser, I have to sign-in again. I don't know, what I have done wrong.
My second question is, how doe Remember Me works as usual? I have some idea but not exactly sure.
Goal
What I want to do:
Create a session for the user
Create a session for the socket (socket.io)
Use passport.js to authenticate the login and socket sessions.
Notes
I have installed MongoStore and passport.socket.io npm's. I can login and set the cookie of the user logged in (connect.sid)
QUESTION
How do I setup the system to store socket sessions and couple them with the session of the user?
Code
app.js
/* The usual express setup */
passport = require('passport'),
LocalStrategy = require('passport-local').Strategy,
User = require('./models/user.js'),
MongoStore = require('connect-mongo')(express);
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({
secret: 'chuck norris',
store: new MongoStore({db: User.name}, // the db's name
function(err) {
console.log(err || 'connect ok!');
})
}));
app.use(express.methodOverride());
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.js (the passport part)
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
},
function(username, password, done) {
User.findOne({username: username}, function(err, user) {
if(!user) {
return done(null, false, {message: 'Incorrect Username!'});
}
if(!user.validPassword(password)) {
return done(null, false, {message: 'Incorrect Password!'});
}
return done(null, user);
});
}
));
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
app.post('/',
passport.authenticate('local'),
function(req, res) {
res.redirect('/home/'+req.user.username);
});
app.js (socket.io part)
io.set('authorization', passportSocket.authorize({
key: 'connect.sid',
secret: 'chuck norris',
store: /* Not entirely sure what goes here */
fail : function(data, accept) { accept(null, false); },
success: function(data, accept) { accept(null, true); }
}));
io.sockets.on('connection', function(socket) {
console.log('User Connected: ' + socket.handshake.user.username);
});
You store your new memory story object instance into a variable and pass it in to both express and socket io like so.
(be aware that we are using different stores but in theory it should not matter what store you use as long as you pass off control the proper way)...
var ...
,MemoryStore = express.session.MemoryStore
,sessionStore = new MemoryStore();
then in app.configure you...
app.use(express.session({store:sessionStore,secret:'secret',key:'express.sid'}));
and finally in socket.io configure
io.configure(function (){
io.set("authorization", passportSocketIo.authorize({
key: 'express.sid', //the cookie where express (or connect) stores its session id.
secret: 'secret', //the session secret to parse the cookie
store: sessionStore, //the session store that express uses
fail: function(data, accept) {
// console.log("failed");
// console.log(data);// *optional* callbacks on success or fail
accept(null, false); // second param takes boolean on whether or not to allow handshake
},
success: function(data, accept) {
// console.log("success socket.io auth");
// console.log(data);
accept(null, true);
}
}));
If you have done this correctly and your user successfully authenticates you should then be able to access the session data on the handshake object.
console.log(socket.handshake.user.username);
//or sometimes it might be...
console.log(socket.handshake.user[0].username);
Hope that helps.