I have read everything on the argument but still cannot understand it. The documentation on the Passport Js web site is very vague.
I am using Passport JS with the passport-ldapauth Strategy. I do not have a Database. I obviously don't want to hit the LDAP server on each request. I would like to authenticate the user the first time on the POST /login route using the passport strategy with LDAP, store the user in the session and on each subsequent requests I just want to check if the user is already logged in.
I am trying to use the session but I cannot understand how to use Passport + session with the serialize/deserialize flow. Every example I checked use a User.findOne in the deserializeUser function.
As of now I disabled the use of the session for Passport and I am using a custom middleware where I check if req.session.user != null. If that's the case the user is already logged in and I hit next(). Otherwise redirect to login.
Here is some code (for sake of simplicity I deleted the code not related to the question):
Passport configuration:
var express = require('express'),
session = require('express-session'),
passport = require('passport'),
LdapStrategy = require('passport-ldapauth');
var app = express();
var LdapStrategyOptions = {
server: {
url: '<url>',
bindDN: '<dn>',
bindCredentials: "<pwd>",
searchBase: '<searchBase>',
searchFilter: '<filter>'
}
};
passport.use(new LdapStrategy(LdapStrategyOptions));
app.use(cookieParser());
app.use(session({
store: new LokiStore({autosave: false}),
resave: false,
saveUninitialized: false
secret: env.get("SESSION_SECRET")
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(passport.initialize());
Routes:
// LOGIN ROUTE
app.get('/login',
function(req, res) {
res.render('login');
});
// LOGIN HANDLER ROUTE
app.post('/login',
passport.authenticate('ldapauth', { session: false }),
function(req, res) {
req.session.userId = req.user.cn;
req.session.user = {
"userId": req.user.cn,
"displayName": req.user.displayName
};
res.redirect('/');
});
// LOGOUT ROUTE
app.get('/logout',
function(req, res) {
req.session.destroy(function(err) {
req.logout();
res.redirect('/');
});
});
// HOME ROUTE
app.get('/', isLoggedIn, function(req, res) {
res.render('home');
});
IsLoggedIn Middleware:
var isLoggedIn = function(req, res, next) {
if (req.session.user != null){
console.log("is auth ok '" + req.session.user.userId +"'");
return next();
}
console.log("redirect to auth/login");
res.redirect('/auth/login');
}
What am I missing? Is there any security fault in my setup?
Any help is appreciated.
From passportjs docs:
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, but rather the unique cookie that identifies the session. In order to support login sessions, Passport will serialize and deserialize user instances to and from the session.
Basically serializeUser is supposed to return a unique user identifier so you can deserializeUser back into JSON later.
So for your implementation you should probably do something along these lines:
DISCLAIMER: I have no experience with LDAP.
passport.serializeUser(function(user, done) {
//We can identify the user uniquely by the CN,
//so we only serialize this into the session token.
done(null, user.cn);
});
passport.deserializeUser(function(cn, done) {
//Directly query LDAP.
//I'm not sure passport caches the result (only calls deserializeUser for new sessions)
//but worst case you can cache the result yourself.
somehowLoadUserFromLDAPByCN(cn, function(err, user) {
done(err, {
userId: user.cn,
displayName: user.displayName
});
});
});
If you only need an id and a display name, it's totally fine to keep them in session. You should only load the full user profile when you need more fields.
Related
Let's say i have several routes in express like,
app.use('/login', login);
app.use('/blogs', blog);
app.use('/news', news);
app.use('/profile', profile);
Here, login, blog, news, profile is routes .js files which contains different get/post routes. I.E. /blogs/new or blogs/:id/edit etc.
Suppose I have 2 users in my system, A and B.
A can access /blogs and /news but can't access /profile
B can access /profile but not the other two.
And /login can be accessed by any user.
How do i do this with express and node?
Is there any way i can set a user to be permitted only to access /profile route and it's children route?
P.S i don't want to use passport.
You can use passport.js for this purpose. Here is an example code for adding passport to your application.
1)Add following lines to packages.json
"passport": "^0.4.0",
"passport-jwt": "^3.0.1",
2). And the following code in server.js. Passport.js has multiple strategies for authentication. Here is configuration for jwt
var passport = require('passport');
app.use(passport.initialize());
require('./server/config/passport')(passport);
var auth = passport.authenticate('jwt', { session: false });
app.use('/your/path/*', auth, function (req, res, next) {
next();
});
Passport with check all the paths like /your/path/* ( i hope you know that).
I will demonstrate this with the cookie session. first lets set up a session. i will be using mongod, for different databases, express-session has different packages to save the session.
//------ app.js----------
const session = require("express-session");
const MongoDbStore = require("connect-mongodb-session")(session);
const store = new MongoDbStore({
uri: "mongodb://localhost:27017/yilmaz",
collection: "sessions",
});
// ------ this adds "session" property to the req
app.use(
session({
secret: "anyhting long",
resave: false,
saveUninitialized: false,
store: store,
})
);
First step of authentication is signing up. We save user's email and password. Next step user logs in the system. In this step we check if the email (or username) and password are in match.
exports.postLogin = async (req, res, next) => {
const email = req.body.email;
const password = req.body.password;
const user = await User.findOne({ email });
if (!user) {
return res.redirect("/login");
}
const match = await bcrypt.compare(password, user.password);
if (match) {
// when we protect the routes we look at this property
// session property is attached by the express-session
// -------THIS IS WHERE WE SAVE THE SESSION TO DATABASE
req.session.isLoggedIn = true;
req.session.user = user;
// after req.session.user is set, everytime client makes a request, our middleware will be checking this
return req.session.save((err) => {
console.log("error in postLogin", err);
return res.redirect("/");
});
}
res.redirect("/login");
};
with checking `req.session.isLoggedin` you can write a middleware and used on the routes.
module.exports = (req, res, next) => {
if (!req.session.isLoggedin) {
return res.redirect("/login");
}
next();
};
in any route you want u can add this middleware
const isAuth = require("../middleware/is-auth");
router.get("/edit-product/:productId", isAuth, adminController.getEditProduct);
I am novice to Node passport authentication. I completely write an example of passport authentication as below:
var express = require('express');
var passport = require('passport');
var passportLocal = require('passport-local');
/*
Since express doesn't support sessions by
default you need following middlewares.
*/
// For storing session ID in browser
var cookieParser = require('cookie-parser');
// For rendering credentials from request bodies
var bodyParser = require('body-parser');
/*
For server side storage of session information.
All these session information is in server memory.
If the machine app reboot all sessions infromation
will disapear. The information coming with request
, thanks to cookies, with the session information
stored in the server used in deserializing the users.
Be careful properly handle session information
in server farms, round robbining and load balancing etc.
But it is easy to configure express-session middleware
to use external storage. In this case we use local machine.
*/
var expressSession = require('express-session');
var app = express();
app.set('view engine', 'ejs');
// For sessions. Need before Passport middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(expressSession({
// This is the secret used to sign the session ID cookie.
secret: process.env.SESSION_SECRET || 'd7n5ihsxx9by8ydt',
resave: false,
saveUninitialized: false
}));
// Passport need 2 middlewares
app.use(passport.initialize());
app.use(passport.session());
/*
Strategies need to be told how to veryfy username and password
that is inside authorization header that is client is goint to send.
*/
function verifyCredentials(username, password, done) {
/*
Use crypto.pbkdf2(password, salt, iterations, keylen[, digest], callback)
For the real app.
*/
if (username === password) {
/*
done() first argument is Error
Second argument 'user' is any object required for your business logic.
But make it as much as smaller for fast serializing and deserializing.
*/
done(null, {
id : 123,
name: username,
role: 'Admin',
catogery : 'Operator'
});
} else {
done(null, null);
}
};
/*
Now we have to configure passport. Call the local strategy with
single function function(username, password, done) where done is callback
funciton.
*/
passport.use(new passportLocal.Strategy(verifyCredentials));
/*
passport.serializeUser serializes the 'user' object.
The callback function done, need a small piece of
'user' object which is required in deserializing.
In following case 'user.id' is saved to session
req.session.passport.user = {id:'..'}
*/
passport.serializeUser(function(user, done) {
done(null, user.id);
});
/*
In deserialize function you provide in first argument of
deserialize function that same key of user object that was
given to done function in serialize call. So your whole object is
retrieved with help of that key. that key here is id.
In deSerialize function that key is matched with in
memory array / database or any data resource.
*/
passport.deserializeUser(function(id, done){
/*
Query database or cache here! Example
User.findById(id, function(err, user) {
done(err, user);
});
*/
done(null, {
id : id,
name: id,
role: 'Admin',
catogery : 'Operator'
});
});
/*
Middleware for API's. 3rd party middlewares are available
to download and install if you want. Middleware is
nothing but a simple function with the following format.
'next' is callback function which is used to pass the request
to the next middleware inline.
*/
function ensureAuthenticated(req, res, next){
if(req.isAuthenticated()){
next();
} else {
res.status(403).json( {msg: '403 Forbidden'} );
}
}
app.get('/', function(req, res) {
res.render('index', {
/*
Express doesn't have isAuthenticated for req.
But passport add it to req. Also the 'user' object
added in done() callback is available through req.
*/
isAuthenticated : req.isAuthenticated(),
user : req.user
});
});
app.get('/login', function(req, res) {
res.render('login');
});
app.get('/logout', function(req, res) {
//Passport add logout method to request object
req.logout();
res.redirect('/');
});
/*
In this end point, the middleware passport.authenticate('local') is called.
passport.authenticate('local') returns a functon similar to ensureAuthenticated.
*/
app.post('/login', passport.authenticate('local'), function(req, res) {
res.redirect('/');
});
/*
Second endpoint for API's. The endpoint is authenticated
with a middleware which is between URI and function.
*/
app.get('/api/data', passport.authenticate('local'), function(req, res) {
res.json([
{name: 'My'},
{name: 'Kumara'}
]);
});
app.get('/api/data/me', function(req, res) {
res.json([
{name: 'My'},
{name: 'Kumara'}
]);
});
var port = process.env.PORT || 3000;
app.listen(port, function() {
console.log('Server is running on Port : ' + port);
})
My question:
I have two URI's:
/api/data
/api/data/me
I assumed that since I have authenticated /api/data it will automatically authenticate /api/data/me since /api/data part is already authenticated, /api/data/me is like a child of /api/data. But it's not. Does this mean do I have to authenticate each and every API's?
If not, how can I group set of API's in a single strategy?
A wildcard could be used to match everything under a certain path. For example:
app.get('/api/data/*', passport.authenticate('local'), function(req, res, next) {
next(); // call next matching route
});
app.get('/api/data/me', function(req, res) {
res.json([
{name: 'My'},
{name: 'Kumara'}
]);
});
This enables the passport middleware for all requests under /api/data/ so that the user will need to authenticate using the passport local strategy. Logic for individual endpoints under /api/data/ can then be implemented without including the passport middleware.
There's more information about express route matching on the user guide. http://expressjs.com/en/guide/routing.html
I am trying to implement sign-in with google and passport but I am running into a bit of a problem. I successfully authenticate with google, but my data isn't being passed to the front end. I Haven't changed anything from the original code except for the URI and necessary client id and secret. Can anyone tell me what I am missing?
var express = require( 'express' )
, app = express()
, server = require( 'http' ).createServer( app )
, passport = require( 'passport' )
, util = require( 'util' )
, bodyParser = require( 'body-parser' )
, cookieParser = require( 'cookie-parser' )
, session = require( 'express-session' )
, RedisStore = require( 'connect-redis' )( session )
, GoogleStrategy = require( 'passport-google-oauth2' ).Strategy;
// API Access link for creating client ID and secret:
// https://code.google.com/apis/console/
var GOOGLE_CLIENT_ID = "307841191614-1shiak514mrjugtbon3dm2if8hbhnvdv.apps.googleusercontent.com"
, GOOGLE_CLIENT_SECRET = "fgViegEgHWuoc1X-p63iPmpF";
// 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. However, since this example does not
// have a database of user records, the complete Google profile is
// serialized and deserialized.
passport.serializeUser(function(user, done) {
done(null, user);
console.log("User: "+ user.displayName); // If there is a persistent session, the console logs out the displayName
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
// Use the GoogleStrategy within Passport.
// Strategies in Passport require a `verify` function, which accept
// credentials (in this case, an accessToken, refreshToken, and Google
// profile), and invoke a callback with a user object.
passport.use(new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
//NOTE :
//Carefull ! and avoid usage of Private IP, otherwise you will get the device_id device_name issue for Private IP during authentication
//The workaround is to set up thru the google cloud console a fully qualified domain name such as http://mydomain:3000/
//then edit your /etc/hosts local file to point on your private IP.
//Also both sign-in button + callbackURL has to be share the same url, otherwise two cookies will be created and lead to lost your session
//if you use it.
callbackURL: "http://127.0.0.1:3000/oauth2callback",
passReqToCallback : true
},
function(request, accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// To keep the example simple, the user's Google profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the Google account with a user record in your database,
// and return that user instead.
console.log(profile); //logs google profile successfully
return done(null, profile);
});
}
));
// configure Express
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use( express.static(__dirname + '/public'));
app.use( cookieParser());
app.use( bodyParser.json());
app.use( bodyParser.urlencoded({
extended: true
}));
app.use( session({
secret: 'cookie_secret',
name: 'kaas',
store: new RedisStore({
host: '127.0.0.1',
port: 6379
}),
proxy: true,
resave: true,
saveUninitialized: true
}));
app.use( passport.initialize());
app.use( passport.session());
/*
===
===
===
Here is where the data is not being read.
*/
app.get('/', function(req, res){
res.render('index', { user: req.user });
console.log(req.user); //Output: undefined
});
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 });
});
// GET /auth/google
// Use passport.authenticate() as route middleware to authenticate the
// request. The first step in Google authentication will involve
// redirecting the user to google.com. After authorization, Google
// will redirect the user back to this application at /auth/google/callback
app.get('/auth/google', passport.authenticate('google', { scope: [
'https://www.googleapis.com/auth/plus.login',
'https://www.googleapis.com/auth/plus.profile.emails.read']
}));
// GET /auth/google/callback
// 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.
app.get( '/oauth2callback',
passport.authenticate( 'google', {
successRedirect: '/',
failureRedirect: '/login'
}));
app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
server.listen( 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');
}
`
Here is the simple layout that doesn't seem to be receiving any data.
<% if (!user) { %>
<h2>Welcome! Please log in.</h2>
<% } else { %>
<h2>Hello, <%= user.displayName %>.</h2>
<% } %>
Your code works, I just used same example on my app.
I had the same problem and I realized that I'm not using a valid account in my tests.
This API retrieves data from Google+ profile. Are you using a valid Google account with linked Google+ profile to authenticate?
I am upgrading the express 4 and my passport is failing every time now. It is not even logging to the console in passport.use(new LocalStrategy.
It redirects the /failure every time without hitting any breakpoints
// 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) {
console.log("LocalStrategy working...");
// asynchronous verification, for effect...
process.nextTick(function() {
// Find the user by username. If there is no user with the given
// username, or the password is not correct, set the user to `false` to
// indicate failure and set a flash message. Otherwise, return the
// authenticated `user`.
findByUsername(username, password, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
message: 'Unknown user ' + username
});
} else {
return done(null, user);
}
})
});
}
));
app.use(cookieParser('keyboard cat'));
app.use(session({
secret: 'keyboard cat',
saveUninitialized: true,
resave: true
}));
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
app.post('/login', passport.authenticate('local', {
failureRedirect: '/failure',
failureFlash: false
}),
function(req, res) {
res.cookie('userdata', req.user);
switch (req.user.role) {
case 'candidate':
res.redirect('/app/candidates');
break;
case 'employer':
res.redirect('/app/employers');
break;
case 'provider':
res.redirect('/app/providers');
break;
case 'admin':
res.redirect('/app/admin');
break;
default:
break;
}
});
Assuming you have all relevant code included the reason for the failure is likely missing body parser. The authentication strategy will try to find the username and password fields from req.body and req.query, and if there is no body parser used req.body will be empty. The strategy will then fail straight away as it would have nothing to pass to your verify callback.
You need to make the Express application use relevant body parser, for example:
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
Have you included LocalStrategy?
var LocalStrategy = require('passport-local').Strategy;
app.use(express.cookieParser()); // read cookies
app.use(express.bodyParser()); // get information from html forms
I'm having an issue where despite logging in successfully (as evidenced by the successRedirect handler triggering), the request is still not authenticated, so my authentication middleware sends us back to the login page again.
My routes look as follows:
// ROUTES
module.exports = function(app, passport) {
app.get('/home', isLoggedIn, function(req, res) {
res.sendfile('imconfusedindex.html'); // this never gets sent.
});
app.post('/login', passport.authenticate('ldap-auth', {
successRedirect: '/home',
failureRedirect: '/login'
}));
}
// route middleware to make sure a user is logged in
function isLoggedIn(req, res, next) {
// if user is authenticated, we'll all float on OK
if (req.isAuthenticated()) {
return next();
}
// otherwise, redirect them to the login page
res.redirect('/login');
}
And my passport configuration looks like this:
passport.serializeUser(function(user, done) {
done(null, user.user_id);
});
passport.deserializeUser(function(id, done) {
connection.query("select * from users where user_id = " + id, function(err, rows) {
done(err, rows[0]);
});
});
passport.use('ldap-auth', new LocalStrategy(
function(username, password, done) {
done(null, {user_id: 2, username: 'bob'});
})
);
As you can see in the passport configuration, I'm returning a dummy user every time. Doing more debugging shows that the request is being authenticated, but after the redirect, it is no longer authenticated.
Unsure what to do, any ideas would be appreciated.
Sigh I'm really stupid...the answer lurked in another question but when I read it I didn't quite put 2 and 2 together.
I'm lacking an express-session (didn't even include my server config here so even harder to debug). As soon as I set up an session, it worked.
In case someone else has this same issue, make sure that in your app config, you include something like:
var session = require('express-session');
app.use(session({secret: 'dontderplikeme'});
When you add in passport and include app.use(passport.session()), this is the session that it will be using to store credentials into.