I'm working on a web API. The flow should be as follows:
User logs in to website --> Passport authenticates the user --> Passport stores information about the user in a persistent session --> User can access the API as long as the session is valid.
Unfortunately, I can't get Passport to create the persistent session. The login part works (only valid users get through), but Passport doesn't store any information about the session in the client's browser. As a consequence of this, the user cannot access the API from this point on.
The parts of code that are relevant for the problem look as follows:
Server.js:
// call the packages we need
var express = require('express'); // call express
var app = express(); // define our app using express
var bodyParser = require('body-parser'); // for accesing data from POST requests
var passport = require('passport'); // for user authentication
var flash = require('connect-flash'); // for session management
var cookieParser = require('cookie-parser');// for reading cookies
// Setup the database connection
var configDB = require('./config/database.js');
var mongoose = require('mongoose');
mongoose.connect(configDB.url);
// Pull schema for training
var Training = require('./models/training');
// configure app
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
// required for passport
require('./config/passport')(passport); // pass passport for configuration
app.use(require('express-session')({
cookie : {
maxAge: 3600000, // see below
secure: false
},
secret: 'khugugjh',
resave: false,
saveUninitialized: true
})); // session secret
app.use(passport.initialize());
app.use(passport.session());
app.use(flash()); // use connect-flash for flash messages stored in session
...
routes.js:
...
// the login route
router.route('/login').post(
passport.authenticate('local-login'),
function(req, res) {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
console.log('logged in: ' + req.user);
});
...
passport.js:
...
// the serilization and deserilization functions for Passport
passport.serializeUser(function(user, done) {
console.log('Serialize: ' + user);
return done(null, user._id);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
console.log('trying to des');
User.findById(id, function(err, user) {
console.log('Deserialize: ' + user);
return done(err, user);
});
});
...
One reason you may be having issues with this could have to do with the way requests are being made from the client-side. If you are using the ES6 fetch API, then you will have to make sure to pass in an key credentials to the options object with a value of "include".
For example:
fetch('/restricted', {
method: 'get',
credentials: 'include'
});
This fixed the problem for me. Also, one thing to note is that you do not need the cookie-parser middleware if you are using express-session v1.5.0, as it comes packaged with it.
Hope that helps.
Additional resources: https://developers.google.com/web/updates/2015/03/introduction-to-fetch
Related
I am building a REST API using NodeJS and Express, powered by a MongoDB database.
I've been struggling for days now trying to get the right folder structure nailed down. So far, I can connect to my database and add new users without an API, but by simply doing GET, POST, etc. requests. I've seen several tutorials online on how to build API using node, but none of them have a more standardized way for setting their folder structure. And that is the reason why I am having such a hard time making it work given my current folder structure.
Here is my Folder Structure
app
---models
------user.js
---api.js
---routes.js
config
---auth.js
---database.js
---passport.js
public
views
package.json
server.js
Server.js
// server.js
// set up ======================================================================
// get all the tools we need
var express = require('express');
var app = express();
var port = process.env.PORT || 2016;
var mongoose = require('mongoose');
var passport = require('passport');
var flash = require('connect-flash');
var configDB = require('./config/database.js');
// configuration ===============================================================
mongoose.connect(configDB.url); // connect to our database
require('./config/passport')(passport); // pass passport for configuration
app.configure(function() {
// set up our express application
app.use(express.logger('dev')); // log every request to the console
app.use(express.cookieParser()); // read cookies (needed for auth)
app.use(express.bodyParser.json()); // get information from html forms
app.use(bodyParser.urlencoded({ extended: true }));
app.set('views', path.join(__dirname + '/views'));
app.set('view engine', 'ejs'); // set up ejs for templating
// set the static files location /public/img will be /img for users
app.use(express.static(__dirname + '/public'));
// required for passport
app.use(express.session({ secret: 'xxxxxxxxx' })); // 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
});
// routes ======================================================================
// require('./app/routes')(app, passport); // load our routes and pass in our app and fully configured passport
// require('./app/api')(api, passport);
app.use('/', require('./app/routes')(app, passport));
app.use('/api', require('./app/api')(api, passport));
// error handlers
// Catch unauthorised errors
app.use(function (err, req, res, next) {
if (err.name === 'UnauthorizedError') {
res.status(401);
res.json({"message" : err.name + ": " + err.message});
}
next();
});
// launch ======================================================================
app.listen(port);
console.log('Live on port ' + port);
api.js
var User = require('./models/user');
var express = require('express');
var apiRoutes = express.Router();
app.use('/api', apiRoutes);
module.exports = function(apiRoutes, passport){
apiRoutes.get('/testapi', function (req,res) {
res.json({SecretData: 'abc123'});
});
}
Every time I hit the endpoint /testapi I get the error "Cannot GET /testapi"
I think my main issue is how to organize my files and folder properly and import/require them the right way. Can anyone help me figure this out?
Server.js
on this line app.use('/api', require('./app/api')(api, passport));
Here you are telling Express to use ./app/api as an middleware by passing "api" and "passport" as arguments.
where you have defined api variable ?
Lets assume its a typo.. in that case from "app/api.js" you are exporting a function and you trying to execute it in server.js app.use('/api', require('./app/api')(api, passport)); which returns undefined.
Express will be expecting a function as middleware not a return value from function.
app/api.js
on line 4 you have app.use('/api', apiRoutes); which doesn't make any sense, because api.js has no idea about "app".
Cleanup your server.js and api.js and try again
This tutorial might help Node with Express
I am beginner of NodeJS.And just started a simple project where I need a session management concept. So How to manage the session in NodeJS application.
In my project there is two file:- app.js and routes.js.
So where we add the session and how to add ??
app.js file :-
var express = require('express'),
app = express(),
path = require('path');
app.set('views', path.join(__dirname , 'views'));
app.engine('html', require('hogan-express'));
app.set('view engine', 'html');
app.use(express.static(path.join(__dirname,'public')));
require('./routes/routes.js')(express,app);
app.listen (3000 , function(){
console.log("working on the Port 3000");
});
and routes.js file :-
module.exports = function(express, app){
var router = express.Router();
router.get('/', function(req , res , next){
res.render('index',{title: 'Welcome'});
});
}
For the session management we need a middleware 'cookie-parser'.Previously it is the part of express but after express 4.0 and later it is a separate module.
So to access the cookie parser we need to install in our project as :
npm install cookie-parser --save
Then add this into your app.js file as :
var cookieParser = require('cookie-parser');
app.use(cookieParser());
Then we reqired session module. So first of all install the session module by :
npm install express-session --save
Then to enable the session. we add below code in app.js file.
app.use(session({secret:config.sessionSecret, saveUninitialized : true, resave : true}));
Then come to the routes.js file :-
Let us suppose there is a session variable favColor. Now using session set the color and get in the other page. the code is look like :-
router.get('/setColor', function(req , res , next){
req.session.favColor = 'Red';
res.send('Setting favourite color ...!');
});
router.get('/getColor', function(req , res , next){
res.send('Favourite Color : ' + (req.session.favColor == undefined?"NOT FOUND":req.session.favColor));
});
This is all about the session management.We can also learn more about the session :- This Reference
I dont suggest you try to build your own session and use https://github.com/expressjs/session instead which works with express well.
An update on 2019, using express-session 1.15.6 (From 1.5 there's no need to use cookie-parser, session can read and write the cookie directly.)
In app.js:
const app = express()
const session = require('express-session');
const options = {
name: 'foo', // Default is connect.sid
store: this.store, // Default is memoryStore, which is for dev only. Setup redis or memcached for prod
secret: 'bar', // Required, used to sign session id cookie
saveUninitialized: true, // Forces a session that is "uninitialized" to be saved to the store
resave: false, //Forces the session to be saved back to the session store
rolling: true //Force a session identifier cookie to be set on every response
};
// Session method will return a middleware function.
const middleware = session(options);
// Now we can make use of session in all the requests
app.use(middleware)
In routes.js or in any handler file created for specific route:
handler1(req, res, next) {
req.session.someField = 'foo';
// Use save method to update the store immediately, if there's other AJAX call pending.
req.session.save();
}
handler2(req, res, next) {
console.log(req.session.someField);
}
handler3(req, res, next) {
// we use delete operator here.
delete req.session.someField;
}
I have a ExpressJS app that is using Passportjs to authenticate with Facebook and everything is working as expected exception for one issue.
I have vehicle.js under /routes/ which contains some routes (router.gets and router.posts) that need authentication and some that don't. If user is logged in then every request handled by vehicle.js causes User de-serialization which is a Mongoose lookup. How can I avoid these unnecessary Mongoose lookups when request is made to a router.get and/or router.post that do not need authentication?
I have already looked up this SO question and it does not address my problem (I have declared static resources above passport, so they are not authenticated).
Passport configs in app.js are shown below:
// Configuring Passport
var passport = require('passport');
var expressSession = require('express-session');
app.use(expressSession({secret: 'thisIsSecret'}));
app.use(passport.initialize());
app.use(passport.session());
// Using the flash middleware provided by connect-flash to store messages in session
// and displaying in templates
var flash = require('connect-flash');
app.use(flash());
// Initialize Passport
var initPassport = require('./passport/init');
initPassport(passport);
//passing passport object could be the reason why all requested that are
//mapped in vehicle cause user de-serialization. Not sure if there is any
//alternative approach than following line that passes passport??
var vehicle = require('./routes/vehicle')(passport);
The following isAuthenticated is in vehicle.js
var isAuthenticated = function (req, res, next) {
if (req.isAuthenticated())
return next();
// if the user is not authenticated then redirect him to the login page
res.redirect('/vehicle/api/login');
}
Followed by a series of routes that handle logging in, logging out, as well as some actions on vehicle.
module.exports = function(passport) {
router.get('/api/login', function(req, res) {
res.render('vehicle/login', { message: req.flash('message') });
});
router.post('/api/login', passport.authenticate('login', {
successRedirect: '/vehicle/api/list/mine',
failureRedirect: '/vehicle/api/list',
failureFlash : true
}));
...
...
...
router.post('/api/upload', isAuthenticated, function(req, res) {
//this route needs to be authenticated, so it works fine,
//deserialization done, mongoose lookup, no problem
});
router.get('/api/image/:vehicleId/:filename', function(req,res) {
//this route does not need authentication, but causes User
//de-serialization and Mongoose lookup
});
return router;
}
Is it because of the following line that every request to vehicle.js causes User de-serialization when a user is logged in?
var vehicle = require('./routes/vehicle')(passport);
One way to avoid such unnecessary de-serialization would be to separate routes that do not need authentication from vehicle.js to a different file and do not pass that passport object to that file (as it is passed to vehicle.js in app.js). I don't know if that is the correct way to resolve this issue.
You can wrap the passport middleware inside a custom middleware that only invokes it for your specified routes. So Instead of:
app.use(passport.session());
you could:
app.use(function(req, res, next){
if(req.url.match('api/image'))
next(); // do not invoke passport
else
passport.session()(req, res, next)
// same as doing == app.use(passport.session())
});
If you use passport.session() middleware, deserialize will happen for every route:
https://github.com/jaredhanson/passport/blob/33075756a626999c6e2efc872b055e45ae434053/lib/strategies/session.js#L53-L69
The solution would be to add it only to ones which use passport.
I've got an app which has a front end/website serving logic on server A in Express, and backend logic with POST endpoints written in Express again on server B.
I've got withCredentials set to true on all my AngularJs methods which call the backend.
In the backend I've got this
var cors = require('cors');
var whitelist = 'http://serverA.com';
var corsOptions = {
origin: whitelist,
methods: ['GET', 'POST', 'OPTIONS', 'PUT'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['Content-Range', 'X-Content-Range'],
credentials: true
};
app.options('*', cors(corsOptions));
app.use(cors(corsOptions));
I try logging in from the client side and the login succeeds and redirects to the correct page but the isAuthenticated method on Passport returns false and it redirects me back to the login page.
What do you think the reason is that the session is not being stored on the client side. The cookie value seems to be set correctly (Chrome dev tools.)
Here is some client code. This is a middleware method used to protect pages for authenticated users.
function authenticate_func(req, res, next) {
//return next();
if (req.isAuthenticated())
{
console.log("Login Successful");
return next();
}
console.log('Login Unsuccessful');
res.redirect('/');
};
Here are middlewares I'm using and the order they are used in.
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
require('/local_strategy')(passport, my_sql);
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
Am I missing anything?
I've seen many variations of this question, but none seemed to solve my issue. I'm trying to set up a Node.js server using Express. Here is my server configuration:
var express = require('express'),
RedisStore = require('connect-redis')(express);
var app = express();
app.use(express.urlencoded());
app.use(express.json());
app.use(express.cookieParser());
app.use(express.session({
store: new RedisStore(),
secret: APP_SECRET
}));
// Initialize redis connection
var client = redis.createClient();
client.on('connect', function() {
console.log('Connected to Redis server')
})
client.on('error', function (err) {
console.log('Error ' + err);
});
// Enable cross-origin resource sharing
app.all('*', function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
next();
});
var api = require('./controllers/api.js');
app.post('/login', api.login);
app.get('/auth', api.auth);
app.listen(3000);
And here are some simple routes:
exports.login = function(req, res) {
var user = new User(req.username, req.password);
req.session.user = user;
console.log(req.session.user); //works
res.json({user:user});
}
exports.auth = function(req, res) {
console.log(req.session.user); //doesn't work
res.json(req.session.user);
}
So in my login route, I can print the session variable as expected. But if I visit the auth route after visiting the login route, the session variable is undefined. How can I get Express sessions to work?
In a typical web application, the credentials used to authenticate a user will only be transmitted during the login request. If authentication succeeds, a session will be established and maintained via a cookie set in the user's browser.
Each subsequent request will not contain credentials or all user data, but rather the unique cookie that identifies the session. In order to support login sessions, You have to serialize and deserialize user instances to and from the session in every request.
In your case, you have assigned req.session.user = user; only in /login request. It will not be available for further requests(/auth).
You have to get user information in /auth request too by session id. (Or) Better you can use passport for authentication.
I think maybe your redis client is not connecting well, try something like this and be sure to start the redis service
sudo service redis-server start
or the way you are calling The RedisStore variable look at the example
example:
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');
var session = require('express-session');
var RedisStore = require('connect-redis')(session);
app.set('port',process.env.PORT || 3000);
app.use(cookieParser());
app.use(session({
resave: true,
saveUninitialized: true,
store: new RedisStore({
host: 'localhost',
port: 6379
}),
secret: 'some string/hash secret'
}));
var counter=0;
app.get('/', function(request, response){
//adding some value to request.session
counter = counter+1;
request.session.color = {'anyValue': counter};
//
console.log('Session ID: ', request.sessionID);
console.log('Session: ', request.session);
response.send('some text counter: '+request.session.color['anyValue']);
});
app.listen(app.get('port'));
The currently accepted answer didn't realize that express.session already handles cookie based sessions with the req.session object. I tried a trimmed down version of yours not using redis and it worked. Looking at the connect-redis docs it looks like you need to pass a session to connect-redis. You are currently passing it express. I believe changing that will fix your problem.
P.S. I would update your node/express versions as current versions of express no longer have the built in middleware along with other improvements.
Newer versions of express:
var session = require('express-session');
var cookieParser = require('cookie-parser');
var json = require('express-json');
var bodyParser = require('body-parser')
Rather than:
express.session
express.cookieParser
express.json
express.bodyParser