I am writing an application using Nodejs and passportjs with Google Strategy. After logging out, the user is still able to login again without entering credentials. How can the user be made to enter credentials again? This can be a big issue on shared computers.
The front end is built using create-react-app that runs on port 3000. For requests related to authentication, a proxy running on port 5000(nodejs server) is used. The code given below is hosted on port 5000
app.use(
session({
cookie: {maxAge: 30 * 24 * 60 * 60 * 1000},
secret: [keys.cookieKey],
resave: false,
saveUninitialized: false
})
);
app.use(cookieParser());
app.use(passport.initialize());
app.use(passport.session());
passport.use(
new GoogleStrategy(
{
clientID: googleClientID,
clientSecret: googleClientSecret,
callbackURL: '/auth/google/callback',
proxy: true
},
async (accessToken, refreshToken, profile, done) => {
const existingUser = await User.findOne({googleId: profile.id});
if(existingUser){
return done(null, existingUser);
}
const user = await new User({ googleId : profile.id}).save();
done(null, user);
}
)
);
app.get('/api/logout', (req, res) => {
console.log(req.user.accessToken);
if(req.session.passport){ delete req.session.passport; }
req.session.destroy(function (err) {
req.logout();
req.logOut();
req.user = null;
res.clearCookie('connect.sid');
res.redirect('/');
});
});
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
["/api", "/auth/google"],
createProxyMiddleware({
target: "http://localhost:5000",
})
);
};
For advanced configurations like logout, passport,a proper session configuration is the key:
Try with this:
app.use(session({
secret: "changeme",
resave: true,
saveUninitialized: true,
rolling: true,
cookie: {
secure: false,
maxAge: (5 * 1000)
}
}));
This was used in
https://github.com/utec/geofrontend-server
A mini server implementation for client side javascript applications, like your react app. I think it will useful for you. Also you can customize the security!
Related
Problem: Everything works fine in development but also in the normal google chrome browser. However when I try incognito and Firefox it sends two different session ids. I cant find a good reason why the session ID changes on the callback URL. I can see that the correct information gets stored, but when a user logs in on firefox the session key is different when the user is on the callback URL than what it is when the user on the home page. The key is however the same everytime I refresh the site but this does not help me as during login the session key is wrong. As I said it works perfect in development.
I have activated withCredentials on the front end.
I am just super confused as it works fine on google chrome but not on anything else.
Here is my code:
Server.js
... //a bunch of imports
app.use(cors(corsOptions));
//SESSION SETUP TOOLS
app.use(cookieParser());
app.use(express.json())
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.json())
//DDOS PROTECTION
app.use(compression())
app.use(helmet())
app.use(limiter);
//SESSION SETTINGS
app.set('trust proxy', true);
app.enable('trust proxy')
const sessionMemStore = () => {
if (isProduction) {
const firestore = new FirestoreStore({
dataset: new Firestore(),
kind: 'express-sessions',
});
return firestore
}
else {
return null
}
};
app.use(
session({
...sessionSettings,
store: sessionMemStore()
})
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
// Initiate Strategy
passport.use(
new SteamStrategy(
{
returnURL: BACKEND_URL + "/api/auth/steam/return",
realm: BACKEND_URL,
apiKey: "B14DD3E47A70AC859EE73AB2C656CB34",
},
function (identifier, profile, done) {
process.nextTick(function () {
profile.identifier = identifier;
return done(null, profile);
});
}
)
);
app.use(passport.initialize());
app.use(passport.session());
app.get(
"/api/v1/auth/steam",
passport.authenticate("steam", { failureRedirect: FRONTEND_URL + "/tos" }),
function (req, res) {
res.send(req.user);
}
);
app.get(
"/api/auth/steam/return",
passport.authenticate("steam", { failureRedirect: FRONTEND_URL + "/tos" }),
function (req, res) {
logon(req.user);
req.session.steamuser = req.user;
res.redirect(FRONTEND_URL);
}
);
app.listen(port, () => {
console.log("Listening, port " + port);
});
sessionsettings.
const sessionSettings = {
secret: "someRandomKey",
saveUninitialized: true,
resave: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000, httpOnly: true, secure: true, sameSite: isProduction ? "none" : "lax"
},
name: "session"
};
My PassportJS setup doing something weird, I can see req.user after logged-in in the deserialize function but after all req.isAuthenticated() false and no req.user found. I have already referred a bunch of questions regarding this issue on StackOverflow, almost every question on StackOverflow. Nothing works for me, not sure what is the case here. I'm posing my code, can someone pls tell me what is wrong here and why it's happening. At least a fix! :(
I'm using passport-google-oauth as my strategy. Also, the client is an Angular app.
I also tried with passport-google-oauth20 by accessing directly.
What I found is, in social_logins.google_callback the req.user can
be found and also req.isAuthenticated() returns true. After the
redirect happens it won't work.
Thanks in advance!
// app.js
const pgSession = require('connect-pg-simple')(session);
app.set('trust proxy', 1);
/** #type {session.CookieOptions} */
const cookieOptions = {
path: '/',
secure: false,
httpOnly: false,
maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days
};
app.use(session({
secret: require('./config').session.secret, // session secret
cookie: cookieOptions,
proxy: true,
name: config.session.name,
resave: false,
saveUninitialized: true,
store: new pgSession({
pool: db.pool,
tableName: 'sess'
}),
}));
require('./config/passport')(passport);
app.use(passport.initialize());
app.use(passport.session());
// passport.js
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser(async (user, done) => {
done(null, user);
});
// passport login.js
app.get('/social-logins/connect/google', passport.authenticate('google-login', {
successRedirect: '/social-logins/google',
failureRedirect: '/social-logins/google',
scope: ['profile', 'email'],
failureFlash: true,
}));
// routes/index.js
router.get('/social-logins/google', social_logins.google_callback);
// callback funtion
social_logins.google_callback = async (req, res) => {
try {
const { user } = req;
if (!user) return res.redirect('https://localhost:3000/auth/login?message=Login Failed');
const url = `https://localhost:3000/auth/dashboard`;
req.session.save(() => {
return res.redirect(url);
});
} catch (error) {
log_error(error);
res.redirect(`https://localhost:3000/auth/login?error=true`);
}
};
// passport strategy
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
module.exports = new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: 'https://localhost:3000/social-logins/connect/google',
passReqToCallback: true
}, async (req, accessToken, refreshToken, profile, done) => {
try {
const acc = {}; // find from db and assign db.query('select * from users where id = $1', [profile.id]);
return done(false, acc, { message: 'User successfully logged in' });
} catch (error) {
return done(error);
}
});
I resolved this by setting domain to cookieOptions.
I have been trying to figure this out for 2 days now.
when I go to this route '/profile' deserializeUser works fine
However, when the user authinticate and is redirected back to the callback url, deserialzeUser is not called.
my redirect:
router.get('/discord', passport.authenticate('discord'));
//when the user authorize from discord page:
router.get('/discord/redirect', passport.authenticate('discord', {
failureRedirect: '/forbidden',
successRedirect: '/profile'
}));
the user gets redirected to '/profile' which checks if they are authenticated (which will be false since req.user is empty due to deserializeUser not being called) then they will be required to authenticate again which is happening because deserializeUser is not being called.
Profile Route:
function isAuthorized(req, res, next) {
if(req.user) {
console.log("User is logged in.");
console.log(req.user);
next();
}
else {
console.log("User is not logged in.");
res.redirect('/auth/discord');
}
}
router.get('/profile', isAuthorized, (req, res) => {
console.log(req.user);
res.send('You are logged in =) !');
});
I am using postgress database from Heroku+ express-session + passport-discord my session:
// Passport store session
const sessionConfig = {
store: new pgSession({
pool: db,
tableName: 'session'
}),
name: 'SID',
secret:process.env.SECRET,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 7,
sameSite: true,
secure: false // ENABLE ONLY ON HTTPS
}};
app.use(session(sessionConfig))
app.use(passport.initialize());
app.use(passport.session());
I found my mistake.
Just remove
sameSite: true,
from the sessionconfig
I have a problem with express-session. When I try to login I have to save my userdata in cookies and after redirect get it on home page. Now after redirect my cookie is clearing and I have default cookie without some data
const authRoute = require('./routes/auth')
mongoose.connect(
process.env.DB_CONNECT,
{ useUnifiedTopology: true, useNewUrlParser: true },
() => {
console.log('Connected to DB')
});
//Middleware
app.use(express.json())
app.use(cors())
app.use(session({
name: 'sid',
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
secret: process.env.SESS_SECRET,
cookie: {
maxAge: 1000 * 60 * 60 * 2,
sameSite: true,
secure: false
}
}))
app.use('/api/user', authRoute)
Routes file
router.get('/login', (req, res) => {
console.log(req.session);
if (req.session.user) {
res.status(200).send(req.session.user)
} else res.status(403).send({ error: 'You need to login first' })
})
router.post('/login', async (req, res) => {
...
req.session.user = { id: user._id, username: user.name, email: user.email }
req.session.save()
//CREATE AND ASSIGN TOKER
const token = jsw.sign({ _id: user._id }, process.env.TOKEN_SECRET)
res.header('auth-toker', token).send(user)
})
Try disabling the Windows Defender or other anti virus software. Those may not allow the connection to go through
Could someone explain why a new session is being created each time I make a call to my express server.
The problem is that, once a new session is created it doesn't contain the user id against the passport property.
The architecture in the frontend is 2 html pages, which I'm serving via get routes.
(1) html page is the sign in form.
(2) html page is a single page ember app which the user can only access once authenticated (working)
I get and post data via jQuery $.ajax - it's almost as if each time I make a request, I get a new session. Very odd.
partial app.js
server.use(cookieParser());
server.use(bodyParser.json());
server.use(bodyParser.urlencoded());
server.use(express.static(__dirname + '/public'));
server.use(session({
name: settings.sessionName,
secret: settings.secret,
cookie: { maxAge: 2 * 60 * 60 * 1000 },
store: new MongoStore({
db: settings.database.name
})
}));
server.use(passport.initialize());
server.use(passport.session());
require("./server/config/passport.js")(server, passport);
require("./server/routes.js")(server, passport);
passport.js
module.exports = function (server, passport) {
var LocalStrategy = require('passport-local').Strategy;
var User = require('../schemas/user');
passport.serializeUser(function (user, done) {
done(null, user._id);
});
passport.deserializeUser(function (id, done) {
User.findById(id, function (error, user) {
if (!error) done(null, user);
else done(error, null);
});
});
passport.use("local", new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
function (req, email, password, done) {
User.findOne({ 'emailAddress': email }, function (error, user) {
if (error) {
return done(error);
}
if (!user) {
return done(null, false);
}
if (!user.validPassword(password)) {
return done(null, false);
}
return done(null, user);
});
})
);
};
Anything will help at this point. Tearing my hair out!
:)
Okay! I found the solution.
I was not passing the session secret into the cookie parser. Problem Solved!
Here's the fix:
partial app.js
server.use(cookieParser(settings.secret));
server.use(bodyParser.json());
server.use(bodyParser.urlencoded());
server.use(express.static(__dirname + '/public'));
server.use(session({
name: settings.sessionName,
secret: settings.secret,
cookie: { maxAge: 2 * 60 * 60 * 1000 },
store: new MongoStore({
db: settings.database.name
}),
}));
The cookie parser needs to have the same secret as the express session.