Related
I'm having trouble with express-sessions while running on localhost. When I call req.session in development it only retrieves part of my session cookie. But it works fine in production. my app.js file is as follows:
const path = require("path");
const express = require('express');
const cookieParser = require('cookie-parser')
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
const mongoose = require('./db/mongoose');
const PORT = process.env.PORT;
const retrieveUserIdFromRequest = require("./middleware/get-user.middleware");
const MONGODB_URI = `mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PW}#cluster0.mongodb.net/test`
const app = express();
var store = new MongoDBStore({
uri: MONGODB_URI,
collection: 'sessions',
});
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization"
);
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, PATCH, PUT, DELETE, OPTIONS"
);
next();
});
const usersRoutes = require("./routes/users");
const loginsRoutes = require("./routes/logins");
const accountsRoutes = require("./routes/accounts");
const projectsRoutes = require("./routes/projects");
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.use(cookieParser())
app.use("/", express.static(path.join(__dirname,'angular')));
var hour = 3600000
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: store,
cookie: {
path: '/',
httpOnly: true,
secure: true,
maxAge: hour,
expires: new Date(Date.now() + hour),
sameSite: true
}
}));
app.use(retrieveUserIdFromRequest);
app.use((req, res, next) => {
console.log('req.session.user app.js', req.session)
next();
});
app.use('/api/users' , usersRoutes)
app.use('/api/logins' , loginsRoutes)
app.use("/api/accounts", accountsRoutes);
app.use("/api/projects", projectsRoutes);
app.use((req, res, next) => {
res.sendFile(path.join(__dirname, "angular", "index.html"));
});
app.listen(PORT, () => {
console.log("Server is listening on port " + PORT);
})
module.exports = app;
my login route is as follows:
exports.login = async (req, res) => {
await User.findOne({ email: req.body.email })
.then(async(user) => {
let obj = user.toJSON()
obj.instances = await user.buildAccess()
const fetchedUser = await obj;
if (!fetchedUser) {
res.status(200).json({
message: 'Could not find e-mail in database.'
})
} else {
if(user.activated){
loginAndBuildResponse(req, res, fetchedUser);
req.session.isLoggedIn = true
req.session.user = {
_id: fetchedUser.id,
email: fetchedUser.email,
access: fetchedUser.access
}
req.session.save()
} else {
res.status(200).json({
message: 'Your account has not been activated. Please check your email for your activation link.'
})
}
}
});
}
While in development, when I call req.session it does not retrieve the session.isLoggedIn or session.user data as I would expect, or as it does in production. Does anyone have any ideas why this might be the case or where i am going wrong?
I can't get the logout function to work correctly and remove the session from the mongodb.
I'm able to store the session. logout was initially req.session.destroy(); but that kept returning an error. (destroy undefined)
I'm pretty new to nodejs/javascript, I'm trying to learn my apologies in advance for any ignorance!
Any help would be appreciated!!
app.js
const express = require('express'), url = require('url');
const path = require('path');
const dotenv = require('dotenv');
const connectToDatabase = require('./src/db/mongoose');
const helmet = require('helmet');
const router = require('./src/routes/routes');
const openApiDocumentation = require('./src/swagger/openApiDocumentation');
const swaggerUi = require('swagger-ui-express');
const cors = require('cors');
const bodyParser = require("body-parser");
var app = express();
const session = require('express-session');
const MongoStore = require('connect-mongo');
const port = process.env.PORT || 3000;
dotenv.config();
connectToDatabase(); //This removed to simplify the connection
app.use(express.json());
app.use(express.static('src/css'));
app.use(express.static('src/js'));
app.use(express.static('scripts'));
app.use(express.static('controllers'));
app.use(express.static(__dirname + '/views'));
app.use(express.static("views"));
app.use('/', express.static(path.join(__dirname, 'views'),{extensions:['html']}));
app.use('/api', swaggerUi.serve, swaggerUi.setup(openApiDocumentation));
app.use(function(req, res, next){
res.locals.user = req.user;
next();
});
app.use("/v1/login", session({
name: 'auth',
secret: 'mykey',
httpOnly: true,
secure: true,
maxAge: 1000 * 60 * 60 * 7,
resave: false,
saveUninitialized: true,
store: MongoStore.create({
mongoUrl: '<redacted>'
})
}));
app.get('/name', (req, res) => {
let name;
if (!req.session) {
return res.status(404).send();
}
name = req.session.user.name;
return res.status(200).send({name});
})
app.use(function (req, res, next) {
res.locals.session = req.session;
next();
});
router.get("/vl/login", function(req, res) {
if (
typeof reqsession.user != "object" || (
typeof req.session.user.name == "undefined" &&
typeof req.session.user.password == "undefined")
) {
res.render("pages/index");
}
res.redirect("Login");
});
app.use(function(req, res, next) {
console.log('%s %s', req.method, req.url);
next();
});
app.engine('.html', require('ejs').__express);
app.set('view engine', 'ejs');
// index page
app.get('/', function(req, res) {
res.render('pages/index');
});
// about page
app.get('/about', function(req, res) {
res.render('pages/about');
});
app.use(function(req, res, next){
res.locals.user = req.user;
next();
});
app.use(cors());
app.use(express.urlencoded({ extended: true }));
app.use(helmet());
app.use('/v1', router);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.listen(port, () => console.log(`Server is running on Port: ${port}`));
userController.js
login: async (req, res) => {
const { userName, password } = req.body
const user = await userModel.findOne({ userName }).lean()
if (!user) {
return res.json({ status: 'error', error: 'Invalid username/password' })
}
if (await bcrypt.compare(password, user.password)) {
// the username, password combination is successful
const token = jwt.sign(
{
id: userModel._id,
userName: userModel.userName
},
JWT_SECRET
)
const { name } = req.body;
req.session.user = {
name,
isLoggedIn: true
}
try {
await req.session.save();
} catch (err) {
console.error('Error saving to session storage: ', err);
return next(new Error('Error creating user'));
}
return res.json({ status: 'ok', data: token })
}
res.json({ status: 'error', error: 'Invalid username/password' })
},
logout: async (req, res, next) => {
try {
await await req.session == null;
} catch (err) {
console.error('Error logging out:', err);
return next(new Error('Error logging out'));
}
return res.json({ status: 'ok'})
},
routes.js
const express = require('express');
const router = express.Router();
const newUser = require('../controllers/userController');
const company = require('../controllers/companyController');
const upload = require('../controllers/upload');
const auth = require('../middleware/auth');
/* GET response for '/'/*
router.get('/', (req, res) => {
res.redirect('/');
}) */
//generate token
router.get('/token', newUser.generateToken);
//login
router.post('/login', newUser.login);
//logout
router.post('/logout', newUser.logout);
//create service user
router.post('/addServiceUser', newUser.addServiceUser);
//Add user
router.post('/users', newUser.addUser);
//remove user
router.delete('/users/:id',auth, newUser.removeUser);
//Get All users
router.get('/users',auth, newUser.getAllUsers);
//Get User
router.get('/user/:id',auth, newUser.getUser);
//Get first name
router.get('/users/:id/firstName',auth, newUser.getUserFirstName);
//Set first name
router.put('/users/:id/firstName',auth, newUser.setUserFirstName);
//Get last name
router.get('/users/:id/lastName',auth, newUser.getUserLastName);
//Set last name
router.put('/users/:id/lastName',auth, newUser.setUserLastName);
//Get user email
router.get('/users/:id/email',auth, newUser.getUserEmail);
//set user email
router.put('/users/:id/email',auth, newUser.setUserEmail);
//Get user phone
router.get('/users/:id/phone',auth, newUser.getUserPhone);
//set user phone
router.put('/users/:id/phone',auth, newUser.setUserPhone);
//get user age
router.get('/users/:id/age',auth, newUser.getUserAge);
//set user age
router.put('/users/:id/age',auth, newUser.setUserAge);
//set user status
router.put('/users/:id/status',auth, newUser.setUserStatus);
//get user status
router.get('/users/:id/status',auth, newUser.getUserStatus);
//set user level
router.put('/users/:id/level',auth, newUser.setUserLevel);
//get user level
router.get('/users/:id/level',auth, newUser.getUserLevel);
//get user gender
router.get('/users/:id/gender',auth, newUser.getUserGender);
//set user gender
router.put('/users/:id/gender',auth, newUser.setUserGender);
//set user address
router.put('/users/:id/address',auth, newUser.setUserAddress);
//get user address
router.get('/users/:id/address',auth, newUser.getUserAddress);
//get active users
router.get('/users/status/active',auth, newUser.getActiveUsers);
//get inactive users
router.get('/users/status/inactive',auth, newUser.getInActiveUsers);
//get non admin users
router.get('/users/level/intern',auth, newUser.getInternUsers);
//get mentor users
router.get('/users/level/mentor',auth, newUser.getMentorUsers);
//Get Avatar
router.get('/users/:id/avatar',auth, newUser.getUserAvatar);
//Set Avatar
router.put('/users/:id/avatar',auth, upload.single('avatar'), newUser.setUserAvatar);
//Delete Avatar
router.delete('/users/:id/avatar',auth, newUser.removeUserAvatar);
//Add Companies
router.post('/companies/:id/team', auth, company.setUserTeamName);
module.exports= router;
It looks like in your logout function, the way your attempting to "logout" the user is setting req.session to null.
As you've figured out this doesn't act how you've expected. The correct way to logout a user would be calling req.session.destroy() usually accompanied by a redirect back to the login page:
return res.redirect("/login");
This will completely remove or, destroy the session and log the user out.
My index.js Server
// USE STRICT;
const express = require('express');
const app = express();
const session = require('express-session');
const http = require('http').Server(app);
const socket = require('socket.io');
const schedule = require('node-schedule');
const cors = require('cors');
const io = socket(http, {
cors: {
origin: 'http://localhost:8080',
methods: ['GET', 'POST'],
allowedHeaders: ['my-custom-header'],
credentials: true
}
});
const port = 8080;
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/uploads'));
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const mustacheExpress = require('mustache-express');
app.engine('html', mustacheExpress());
app.set('view engine', 'html');
app.set('views', __dirname + '/views');
const secret = 'somesecretkeyhere';
const passport = require('passport');
const helmet = require('helmet');
const { sendMail } = require('./controllers/sellerAdsController');
// Gives us access to variables set in the .env file via `process.env.VARIABLE_NAME` syntax
// require('dotenv').config();
// Must first load the models before passport
require('./models/user');
// Pass the global passport object into the configuration function
require('./config/passport')(passport);
// This will initialize the passport object on every request
app.use(passport.initialize());
// Allows our remote applications to make HTTP requests to Express application
app.use(cors());
app.use(helmet());
app.use(express.urlencoded({ extended: false }));
// app.use(express.json()); //WARNING: Do not turn on. stops formidable for api calls
app.use(cookieParser(secret));
app.use(session({
secret: secret,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: true
}
}));
app.use(csrf());
// Stop page caching
app.use(function (req, res, next) {
res.set('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
next();
});
// Imports all of the routes from ./routes/index.js
app.use(require('./routes/api/v1'));
// Socket Operations
// io.on('connection', io => {
// let sessionId = io.id;
// io.on('clientHandshake', (data) => {
// console.log(data);
// io.emit('serverHandshake', { sessionId: sessionId });
// });
// });
// io.use((socket, next) => {
// const username = socket.handshake.auth.username;
// if (!username) {
// return next(new Error('invalid username'));
// }
// console.log(username);
// socket.username = username;
// next();
// });
io.on('connection', (socket) => {
console.log('👾 New socket connected! >>', socket.id);
// notify existing users
socket.broadcast.emit('user connected', {
userID: socket.id,
username: socket.username,
});
socket.on('private message', ({ content, to }) => {
socket.to(to).emit('private message', {
content,
from: socket.id,
});
console.log(content, to);
});
});
// EROOR HANDLING ROUTES MUST BE BENEATH ALL APP.USE AND ROUTES
// Check if request is from web or app (HTML/JSON)
// Handle 404
app.use(function (req, res) {
res.status(404);
res.render('404.html', { title: '404: File Not Found' });
});
// Handle 500
app.use(function (error, req, res) {
return res.send(error);
// res.status(500);
// res.render('500.html', { title: '500: Internal Server Error', error: error });
});
// SCHEDULED JOBS
const now = new Date();
let date = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 0, 0);
schedule.scheduleJob(date, sendMail);
http.listen(port, () => {
console.log(`listening on *:${port}`);
});
And this is how I am getting from VUE
window.axios.get('/databank/getCSRF').then((response) => {
window.axios.defaults.headers.common['XSRF-TOKEN'] = response.data;
}, (err) => {
console.log(err)
})
And this is my login request header
XSRF-TOKEN from my login request header sent by axios
So Ive set my server up like that, and my vue SPA, but getCSRF() seems to be getting the request but I can't do a POST request back to the server throws an error
ForbiddenError: invalid csrf token
at csrf
Maybe because you wrote XSRF-TOKEN instead of CSRF-Token as it suggests in the Express Documentation.
Hello i am trying to understand and apply "passport-azure-ad" function which i found on https://learn.microsoft.com/en-us/graph/tutorials/node?tutorial-step=1 to my own web applicaton.
Instead of "hbs"template engine what they use in the tutorial i use "jade". I registered the application in Azure. When running the web-application I and noticed that the signin function is working (redirected to the login page of Microsoft to enter my credentials). But when i leave the credentials page it returns to the http://localhost:3000/error instead of **http://localhost:3000/succes ** what i expected.
Can you please help me?
Below i put the javascript files:
app.js
require('dotenv').config();
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var session = require('express-session');
var flash = require('connect-flash');
var passport = require('passport');
var OIDCStrategy = require('passport-azure-ad').OIDCStrategy;
var graph = require('./graph');
// Configure simple-oauth2
const oauth2 = require('simple-oauth2').create({
client: {
id: process.env.OAUTH_APP_ID,
secret: process.env.OAUTH_APP_PASSWORD
},
auth: {
tokenHost: process.env.OAUTH_AUTHORITY,
authorizePath: process.env.OAUTH_AUTHORIZE_ENDPOINT,
tokenPath: process.env.OAUTH_TOKEN_ENDPOINT
}
});
// Configure passport
// In-memory storage of logged-in users
// For demo purposes only, production apps should store
// this in a reliable storage
var users = {};
// Passport calls serializeUser and deserializeUser to
// manage users
passport.serializeUser(function(user, done) {
// Use the OID property of the user as a key
users[user.profile.oid] = user;
done (null, user.profile.oid);
});
passport.deserializeUser(function(id, done) {
done(null, users[id]);
});
// Callback function called once the sign-in is complete
// and an access token has been obtained
async function signInComplete(iss, sub, profile, accessToken, refreshToken, params, done) {
if (!profile.oid) {
return done(new Error("No OID found in user profile."), null);
}
try{
const user = await graph.getUserDetails(accessToken);
if (user) {
// Add properties to profile
profile['email'] = user.mail ? user.mail : user.userPrincipalName;
console.info(users)
}
} catch (err) {
done(err, null);
}
// Create a simple-oauth2 token from raw tokens
let oauthToken = oauth2.accessToken.create(params);
// Save the profile and tokens in user storage
users[profile.oid] = { profile, oauthToken };
return done(null, users[profile.oid]);
}
// Configure OIDC strategy
passport.use(new OIDCStrategy(
{
identityMetadata: `${process.env.OAUTH_AUTHORITY}${process.env.OAUTH_ID_METADATA}`,
clientID: process.env.OAUTH_APP_ID,
responseType: 'code id_token',
responseMode: 'form_post',
redirectUrl: process.env.OAUTH_REDIRECT_URI,
allowHttpForRedirectUrl: true,
clientSecret: process.env.OAUTH_APP_PASSWORD,
validateIssuer: false,
passReqToCallback: false,
scope: process.env.OAUTH_SCOPES.split(' ')
},
signInComplete
));
//setup routes
//require('./routes/routes')(app, passport);
var indexRouter = require('./routes/index');
var authRouter = require('./routes/auth');
var app = express();
app.use(session({
secret: 'your_secret_value_here',
resave: false,
saveUninitialized: false,
unset: 'destroy'
}));
// Flash middleware
app.use(flash());
// Set up local vars for template layout
app.use(function(req, res, next) {
// Read any flashed errors and save
// in the response locals
res.locals.error = req.flash('error_msg');
// Check for simple error string and
// convert to layout's expected format
var errs = req.flash('error');
for (var i in errs){
res.locals.error.push({message: 'An error occurred', debug: errs[i]});
}
next();
});
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Initialize passport
app.use(passport.initialize());
app.use(passport.session());
app.use(function(req, res, next) {
// Set the authenticated user in the
// template locals
if (req.user) {
res.locals.user = req.user.profile;
}
next();
});
app.use('/', indexRouter);
app.use('/auth', authRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
auth.js
var express = require('express');
var passport = require('passport');
var router = express.Router();
/* GET auth callback. */
router.get('/signin',
function (req, res, next) {
passport.authenticate('azuread-openidconnect',
{
response: res,
prompt: 'login',
failureRedirect: '/',
failureFlash: true,
successRedirect: '/'
}
)(req,res,next);
}
);
router.post('/callback',
function(req, res, next) {
passport.authenticate('azuread-openidconnect',
{
response: res,
failureRedirect: '/Error',
failureFlash: true,
successRedirect: '/Succes'
}
)(req,res,next);
}
);
graph.js
var graph = require('#microsoft/microsoft-graph-client');
require('isomorphic-fetch');
module.exports = {
getUserDetails: async function(accessToken) {
const client = getAuthenticatedClient(accessToken);
const user = await client.api('/me').get();
return user;
}
};
function getAuthenticatedClient(accessToken) {
// Initialize Graph client
const client = graph.Client.init({
// Use the provided access token to authenticate
// requests
authProvider: (done) => {
done(null, accessToken);
}
});
return client;
}
So I am trying to build a simple login/authorization tool utilizing node.js, passport.js and angular2. My current issue is that while a user can login, the session information doesn't appear to be passed to the front end server or the front end server doesn't pass the passport information back.
When logging in the user appears to get all the way to the portion where the res.send is called, and at that point serialize has been called and req.sessions.passport.user has been set; however, when the user tries to go to an authorized page, while the cookie is there, the passport is missing. While Deserialized is also never called, the middleware is called/appears called. When the middleware gets to the deserializer there is no passport/user attached thus deserialize is never called.
At this point I am wondering if it might be a CORS issue or something with angular2, but I have been working on this for several days and appear to be doing it the suggested way. I have also tried rebuilding it and setting up CORS in multiple ways along with the middleware and I am running out of ideas. I am also using express session but that appears to be working because the cookie I create in that exists.
Session Data at the end of auth but before responding to the site
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true,
secure: false },
passport:
{ user:
anonymous {
username: 'test',
hash: '4024ca40c4372e029459a1d2d52a25b2fc4642f980f6cc948cc4b35f6350adde',
} } }
Session Data after making further requests
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true,
secure: false } }
Relevant Code:
Passport.js
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((users, done) => {
var id=users.username;
db.one('select * from users where username = $1', id)
.then((user) => {
done(null, user);
})
.catch((err) => { done(err,null); });
});
local.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const init = require('./passport');
var promise = require('bluebird');
var options = {
// Initialization Options
promiseLib: promise
};
var hashclient = require('hashapi-lib-node');
const crypto = require('crypto');
var hash = crypto.createHash('sha256');
var pgp = require('pg-promise')(options);
var connectionString = 'postgresql://...';
var db = pgp(connectionString);
const optionsPassport = {};
init();
passport.use(new LocalStrategy(optionsPassport, (username, password, done) => {
db.one('select * from users where username = $1', username)
.then((user) => {
hash.update(password);
var encryptedPassword=hash.digest('hex');
hash = crypto.createHash('sha256');
if (!user) return done(null, false, { message: 'Incorrect username.' });
if (encryptedPassword!=user.password) {
return done(null, false, { message: 'Incorrect information.' });
} else {
return done(null, user);
}
})
.catch((err) => { return done(err); });
}));
helpers.js
function loginRequired(req, res, next) {
if (!req.user) return res.status(401).json({status: 'Please log in'});
return next();
}
Router.js example
const users = require('express').Router();
const auth = require('./auth');
const update = require('./update');
const password = require('./password');
const authHelpers = require('./helpers');
const passport = require('./local');
users.post('/update', authHelpers.loginRequired, update);
users.get('/:userId', authHelpers.loginRequired, single);
users.post('/create', create);
users.post('/auth', passport.authenticate('local'), auth);
app.js
var passport = require('passport');
app.use(cookieParser())
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({
secret: 'X+a1+TKXwd26mkiUUwqzqQ==',
resave:true,
saveUninitialized:true,
cookie:{secure:false}
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(function (req, res, next) {
var allowedOrigins = ['http://localhost:3000']
res.header('Access-Control-Allow-Origin', allowedOrigins);
res.header( 'Access-Control-Allow-Headers', 'withCredentials, Access-Control-Allow-Headers, Origin, X-Requested-With, X-AUTHENTICATION, X-IP, Content-Type, Accept, Access-Control-Request-Method, Access-Control-Request-Headers');
res.header( 'Access-Control-Allow-Methods', 'GET, OPTIONS, HEAD, POST, PUT, DELETE');
res.header( 'Access-Control-Allow-Credentials', true);
next();
});
var routes = require('./routes');
app.use('/', routes);
front end http service
getData (url:string, data:any): Observable<any> {
var headers = new Headers({ 'Content-Type': 'application/json', withCredentials: true });
var options = new RequestOptions({ headers: headers });
return this.http.get(url,options)
.map((res: Response): data => res.json())
.catch(this.handleError);
}
The issue was on the front end I was not setting withCredentials to true in the correct location
var options = new RequestOptions({ headers: headers, withCredentials: true });