I've hit a bit of a problem getting client-session middleware working in Express. In short the session_state doesn't seem to be accessible when redirecting to new route after being set. For reference I have followed this video tutorial (client-session part starts 36:00 approx.) and double checked my steps but still encountering problems. Middleware is set up as follows:
var sessions = require('client-sessions');
Instantiated with code from the Express website.
app.use(sessions({
cookieName: 'session',
secret: 'iljkhhjfebxjmvjnnshxhgoidhsja',
duration: 24 * 60 * 60 * 1000,
activeDuration: 1000 * 60 * 5
}));
I have the sessions middleware placed between bodyParser and routes if that makes any difference.
Here are the sections my routes/index.js pertaining to the issue. The req.session_state seems to get set ok and the correct user details log to the console.
// POST login form
router.post('/login', function(req, res) {
User.findOne( { email: req.body.email }, function(err,user){
if(!user) {
console.log("User not found...");
res.render('login.jade',
{ message: 'Are you sure that is the correct email?'} );
} else {
if(req.body.password === user.password) {
// User gets saved and object logs correctly in the console
req.session_state = user;
console.log("session user...", req.session_state);
res.redirect('/dashboard');
}
}
//res.render('login.jade', { message: 'Invalid password'} );
});
});
However, something is going wrong when the res.redirect('/dashboard'); is run because the session_state is not accessible when it hits that route. Here is the code for the /dashboard route.
router.get('/dashboard', function(req, res) {
// OUTPUT = 'undefined' ???
console.log("dash...", req.session_state);
// 'if' fails and falls through to /login redirect
if(req.session && req.session_state){
console.log("dash route...", req.session_state);
User.findOne( { email: req.session_state.email }, function
(err, user){
if(!user){
req.session.reset();
res.redirect('/login');
} else {
res.locals.user = user;
res.render('dashboard.jade')
}
});
} else {
res.redirect('/login');
}
//res.render('dashboard', { title: 'Your Dashboard' });
});
Basically, the object stored in session_state is not accessible after the /dashboard redirect. I've been trying to debug it for a day or so without any luck. Any help much appreciated. Sorry if I am missing something obvious. Just getting my feet wet with session middleware so maybe I haven't fully grasped Session or I am overlooking something. Thanks in advance!
I've updated my answer with code that should help you set the cookie and an alternative session manager known as a token. In this example I've provided to parts to a middle ware one part which attaches the cookie (this can be expanded on to determine your use case) and the second part which checks the token for expiration or what ever else might be in there (i.e. audience, issuer, etc.)
app.use('/js/',function(req, res, next) {
//check if the request has a token or if the request has an associated username
if(!req.headers.cookie){
console.log('no cookies were found')
var token = jwt.sign({user_token_name:req.body.user_name},app.get('superSecret'), {
expiresIn: 1 *100 *60 // expires in 1 mintue can be what ever you feel is neccessary
});
//make a token and attach it to the body
// req.body.token = token // this is just placing the token as a payload
res.cookie('austin.nodeschool' , token,{ maxAge: 100000, httpOnly: true }) //this sets the cookie to the string austin.nodeschool
}
if(req.body.user_name){
next()
}else{
res.send('request did not have a username').end() // this is here because my middleware also requires a username to be associated with requests to my API, this could easily be an ID or token.
}
},function(req, res, next) {
// console.log(req.headers) this is here to show you the avilable headers to parse through and to have a visual of whats being passed to this function
if(req.headers.cookie){
console.log(req.headers.cookie) //the cookie has the name of the cookie equal to the cookie.
var equals = '=';
var inboundCookie = req.headers.cookie
var cookieInfo = splitCookie(inboundCookie,equals) //splitCookie is a function that takes the inbound cookie and splits it from the name of the cookie and returns an array as seen below.
console.log(cookieInfo)
var decoded = jwt.verify(cookieInfo[1], app.get('superSecret'));
console.log(decoded)
// You could check to see if there is an access_token in the database if there is one
// see if the decoded content still matches. If anything is missing issue a new token
// set the token in the database for later assuming you want to
// You could simply check if it's expired and if so send them to the login if not allow them to proceed through the route.
}
next()
});
Related
so Ive finally deployed my app and resolved all the CORS issues, but I have a problem with user authentification. I can log-in, but when I refresh a site I get automaticly logged out -> on all browsers beside Mozilla Firefox, there it works somehow.
userContext.js -> Front-end
//XXX Login
const login = () => {
Axios.post(`${apiUrl}/users/login`, {
userName: nameLog,
userPassword: passwordLog,
}).then((response) => {
console.log(response);
if (!response.data.errors) {
setUser(response.data.name);
setUserId(response.data.user_id);
} else {
console.log(response);
const errors = response.data.errors;
console.log(errors);
processErrors(errors);
}
});
};
//Checking if user is logged in on every refresh of a page
useEffect(() => {
Axios.get(`${apiUrl}/users/login`).then((response, err) => {
console.log("GET /login RESPONSE: ", response);
if (response.data.loggedIn === true) {
setUser(response.data.user[0].name);
setUserId(response.data.user[0].user_id);
}
});
}, []);
First call is a POST request, thats when user logs in using form on my site.
And second one is a GET request, that checks if the session returns loggedIn true, this is called on every refresh of a page as it is inside useEffect hook.
Then I update my userState which acts as auth if user is allowed to do some action or not.
userRoutes.js -> Back-end
//Login user
router.post(
"/login",
[
check("userName").exists().notEmpty().withMessage("Username is empty.").isAlpha().isLength({ min: 3, max: 40 }),
check("userPassword").exists().notEmpty().withMessage("Password is empty.").isLength({ min: 3, max: 60 }).escape(),
],
(req, res) => {
const valErr = validationResult(req);
if (!valErr.isEmpty()) {
console.log(valErr);
return res.send(valErr);
}
const name = req.body.userName;
const password = req.body.userPassword;
const sql = "SELECT * FROM users WHERE name = ?";
db.query(sql, name, (err, result) => {
if (err) throw err;
if (!result.length > 0) {
res.send({ errors: [{ msg: "User doesn't exist" }] });
} else {
//compare hashed password from front end with hashed password from DB
bcrypt.compare(password, result[0].password, (error, match) => {
if (error) throw error;
//if passwords match send -> create session and send user data
if (match) {
req.session.user = result;
res.send({ user_id: result[0].user_id, name: result[0].name });
} else {
res.send({ errors: [{ msg: "Wrong username or password" }] });
}
});
}
});
}
);
//Checking if user is logged in and if so, sending user's data to front-end in session
router.get("/login", (req, res) => {
console.log("GET /login SESSION: ", req.session);
if (req.session.user) {
res.send({ loggedIn: true, user: req.session.user });
} else {
res.send({ loggedIn: false });
}
});
Again first one is for the POST request, where I create session and send it in response filled with user's data (name,id) to front-end (then I update my states accordingly).
Second one belongs to the GET request and returns false if user is not logged in or true + user's data. Then once again I update my states.
However this doesnt work and I dont know why. As I mentioned it returns loggedIn: false on every browser besides Mozzilla Firefox.
This is my first time dealing with sessions and cookies so what am I missing here?
By the way the site url is here if that helps: LINK, I left some console.logs() to display responses.
EDIT: adding all middleware
app.js -> main nodejs file
const express = require("express");
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const cors = require("cors");
const { check, validationResult } = require("express-validator");
const userRoutes = require("./routes/userRoutes.js");
const app = express();
app.use(express.json());
app.use(
cors({
origin: [
"http://localhost:3000",
"https://todo-react-node.netlify.app",
],
methods: ["GET, POST, PATCH, DELETE"],
credentials: true, //allowing cookies
})
);
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use((req, res, next) => {
console.log("SESSION 2 : ", req.session);
console.log("Cookies 2 : ", req.cookies);
next();
});
app.use(
session({
key: "userID",
secret: "subscribe",
resave: false,
saveUninitialized: false,
})
);
app.use("/users", userRoutes);
Your useEffect only called once if you are using [] in useEffect function.
Try to take it out(or add the suitable dependencies) and add a console.log() in that function to test out.
Furthermore, read the following article to gain some insight about useEffect.
Okay so finally Ive managed to solve this issue with sessions and cookies. Solution at the end.
What was causing problems?
Cross domain cookies (probably). As Ive mentioned everything worked fine when Ive hosted my app on localhost, but as soon as Ive deployed my app to 2 different hostings (Netlify - front end, Heroku - back end) my cookies werent coming trough.
Ive actually managed to send cookie by
res.cookie("cookie_name", "cookie_value", {sameSite: "none" secure: true})
, however It was standalone cookie and not associated with a session.
Sure why dont you edit your settings in express-session then? Ive tried -> didnt work. My session was never created. I could only send standalone cookie.
And another problem I couldnt delete this cookie in browser after being set, nor I could modify it. Ive tried res.clearCookie("cookie_name") or setting maxAge attribute to -1, nothing worked.
After googling and watching a lot of videos I found out that sending and receiving cookies from different domains is restricted because of security.
SOLUTION -> How did I fix the problem?
Well Ive came upon a VIDEO on YouTube, that showed how to host full-stack application on Heroku. That way your front end and back end are on the same domain and voilĂ sessions and cookies are working properly. Basically it goes like this:
1) Build your React app
2) In /server (main back end folder) create /public folder and move content of your /client/build directory here
3) In your back end main file (app.js in my case) add app.use(express.static("public")); to serve your front end.
4) Change cors settings to:
app.use(
cors({
origin: "your Heroku app url",
credentials: true, //allowing cookies
}));
5) Change express-session settings
app.use(
session({
name: "name of session",
key: "your key",
secret: "your secret",
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
maxAge: 1000 * 60 * 60 * 24,
},
}));
6) Fix your routes for API calls (currently what Im working on)
7) Host it
Create session:
req.session.SESSION_NAME = session_data; Note that SESSION_NAME has to be identical with "name" attribute's value you have declared in step 5
Delete session:
res.clearCookie("SESSION_NAME"); req.session.destroy();
Hope this helps somebody, who encountered this issue.
I am running into an issue with Passport.js in which I want to get the currently logged in users information from a Post request and process some stuff. When I console.log(req.user) it comes up as 'undefined'. The set up and authentication all works, I can also retreive user's info using a Get request as seen from the first code snippet.
router.get('/', function(req , res){
console.log("The current logged in user is: " + req.user.first_name);
res.render('index.ejs' , {
user: req.user
});
});
^ returns user's name as expected
router.post('/testPost' ,function(req , res){
console.log("The current logged in user is: " + req.user);
res.json({
status: "success"
});
});
^returns undefined even when the user is logged in.
I have seen the same question raised here How to get req.user in POST request using passport js two years ago but there was no answer.
It's because the user might not be logged in at the time you're checking it.
To ensure that a user is logged in when accessing a route you should have a middleware that checks it for you.
You can write it as a separate module and import it in every single one of your routes if it's required.
The module:
module.exports = {
EnsureAuthenticated: (req, res, next) => {
if (req.isAuthenticated()) {
return next();
} else {
res.sendStatus(401);
}
}
};
The routes:
//Destructuring | EnsureAuth Function
const {
EnsureAuthenticated
} = require('../path/to/the/module');
//You should get your user here
router.get('/', EnsureAuthenticated, (req, res) => {
console.log(req.user)
});
I am trying to generate a password token through speakeasy.js on a node.js express server, which shall be used as authentication. The password should change every hour. I'm using routers to retrieve the token and verify it.
If have set time to 30 seconds for testing purposes, but the token never changes.
Code:
var secret = speakeasy.generateSecret();
var token = speakeasy.totp({
secret: secret.base32,
encoding: 'base32',
step: : 10
});
router.get('/token/:token', function(req, res) {
console.log(token);
var usertoken = req.params.token;
if(usertoken == token){
res.send("Verified")
} else {
res.send("Not Verified")
}
res.json({ token: token, usertoken: usertoken });
});
Any suggestions?
EDIT
Added step to token.
When i request the end point e.g. http://localhost:8080/api/token/664006 the console shows the toke e.g. 290595. When i refresh the endpoint after a certain amount of time the token should change, but it doesn't. It's still 290595.
I've just checked the documentation and it looks that the parameter you're looking for it's step.
You should keep the time field to the default (Date.now() from the doc) and play with the step field.
Something like this:
var secret = speakEasy.generateSecret();
var token = speakEasy.totp({
secret : secret.base32,
encoding : 'base32',
// leave time field to default
step : 10
});
and for the verification use the method provided, instead of the ==:
router.get('/token/:token', function(req, res) {
console.log(token);
var usertoken = req.params.token;
var verified = speakeasy.totp.verify({
secret: base32secret,
encoding: 'base32',
token: userToken
});
//check if the token has changed
console.log(verified);
});
https://github.com/speakeasyjs/speakeasy
For me, when I added 'step' inside verify code, then everything went working.
var verified = speakeasy.totp.verify({
secret: secret,
encoding: 'base32',
token: token,
step: 10
})
I have an app that uses the MEAN stack, recently I have seen a little strange behaviour. Now this doesn't happen every time a user registers, so far it has happened 3 times. When a user registers the app creates 2 accounts for that user with all the same details. Now I have already added functionality to detect if a user already exists with that email and redirect them towards the login page but doesnt seem to stopping the issue.
Heres my code:
// =========================================================================
// LOCAL SIGNUP ============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
firstNameField: 'firstName',
lastNameField: 'lastName',
usernameField: 'email',
passwordField: 'password',
jobTitleField: 'jobTitle',
startDateField: 'startDate',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({'email': email}, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, {
message: 'That email is already taken.'
});
}
else { var token = crypto.randomBytes().toString();
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.firstName = req.body.firstName;
newUser.lastName = req.body.lastName;
newUser.email = email;
newUser.password = newUser.generateHash(password); // use the generateHash function in our user model
newUser.jobTitle = req.body.jobTitle;
newUser.startDate = req.body.startDate;
newUser.birthday = req.body.birthday;
newUser.region = req.body.region;
newUser.sector = req.body.sector;
newUser.accountConfirmationToken = token;
newUser.accountConfirmationTokenExpires = Date.now() + 3600000;
newUser.accountVerified = 'false';
newUser.isLineManager = 'false';
// save the user
newUser.save(function(err) {
if (err)
throw err;
else {
var data = {
from: 'system',
to: email,
subject: 'Account Verification',
text: 'You recently registered onto the App, to gain access to your account please verify your account.\n\n' +
'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
'http://' + req.headers.host + '/verify/' + token + '\n\n'
};
mailgun.messages().send(data, function(error, body) {
console.log(body);
console.log("setting token 1");
req.flash('info', 'An e-mail has been sent to ' + email + ' with further instructions.');
});
return done(null, newUser);
}
});
}
});
}));
My Conclusions:
I tested the app by creating a test account and once I had filled out the signup form I quickly clicked twice on the signup-now button and when I checked the database it had created 2 accounts with the same details. Basically it sends 2 POST requests to create accounts and both of them get approved. When only 1 should be approved.
My Question:
How can I fix this issue so if the user clicks twice on the signup
button it only creates one account.
Also could there be another reason this might be happening, is there
any issue with the code above?
Thanks.
Edit:
App Config Code:
// configuration ===============================================================
mongoose.connect(database.url); // connect to mongoDB database on modulus.io
require('./config/passport')(passport);
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/views')); // set the static files location /public/img will be /img for users
app.use(busboy());
app.use(compression()); //use compression
app.use(morgan('dev')); // log every request to the console
app.use(bodyParser.urlencoded({'extended': true})); // parse application/x-www-form-urlencoded
app.use(bodyParser.json()); // parse application/json
app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse application/vnd.api+json as json
app.use(methodOverride());
app.use(cookieParser()); // read cookies (needed for auth)
app.set('view engine', 'ejs'); // set up ejs for templating
// required for passport
app.use(session({ secret: ''})); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session
Edit:
Route code:
app.post('/signup', function(req, res, next) {
passport.authenticate('local-signup', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
return res.json({
message: 'An account with this email is already registered. Please try to login, if you cant remeber the password then please use our password reset service'
})
}
req.logIn(user, function(err) {
if (err) {
return next(err);
}
return res.json({
redirectUrl: '/verifyAccount',
message: 'We have sent an email to verify your email. Once you have verified your account you will be able to login'
});
});
})(req, res, next);
});
You can disable the signup button on first press to prevent double click on it.
As a solution I can advice you to set unique constraint on your email column, that won't to allow to insert rows with an existing email
And what about the code that uses LocalStrategy, it should work correctly, but you can just override usernameField, passwordField(So, you can remove other fields). Use req.body in order to get other form data as you've already done (Just in case of refactoring)
Suppose I have a script like this, which uses a Passport authentication strategy with an Express backend. How would I use this script to actually make API function calls? I don't see any explicit examples in the linked project's documentation nor can I find anything in Passport.js's documentation. Thanks.
After passport user serialization done, every request has user field, which contains information passed to done callback of passport.serializeUser method.
app.get('/userID', function (req, res) {
if (req.isAuthenticated()) {
res.json(req.user.id);
}
res.redirect('/login');
}
Also, you have access to session
app.get('/auth/fitbit/callback',
passport.authenticate('fitbit', { failureRedirect: '/login' }),
function(req, res) {
req.session.loggedInAt = Date.now();
res.redirect('/');
});
Information stored in session available in all requests, while user is authenticated
app.get('/someroute', function (req, res) {
// call to another service
var url = 'http://superservice.com/' + req.user.id + '/' + req.session.loggedInAt
http.get(url, function (_res) {
res.send(_res.data)
});
});
I'm supposing that you know how to use passport, and you will figure it out what's the right Fitbit API endpoint (honestly, I'm don't know it). Said that, let me give an idea that might help you solve your problem:
// An awesome npm module (https://github.com/mikeal/request)
var request = require('request');
//
//
//
// An express route.
app.get('/activities', function (req, res) {
if (req.user !== null) {
// User is authenticated.
getUserActivities(req.user.id, res);
} else {
// Redirect to login the user isn't authenticated.
res.redirect('/login');
}
});
// This function will make API calls to Fitbit
// using the User ID we got from the PassportJS
// authentication process.
function getUserActivities(id, res) {
// It will request from Fitbit User activities.
request('https://api.fitbit.com/1/user/'+ id +'/activities/',
function (error, response, body) {
if (!error && response.statusCode == 200) {
// If everything goes well.
return res.send(body);
} else {
// If something wrong happens.
return res.send(error);
}
);
}
The goal of this example is to show you that you need to use PassportJS to get fitbit users ID, then use that id to make API calls to fitbit.