how to use passport.js in a proper way - node.js

I am trying passport library to authenticate api request. To start I have created a NodeJS application with the express framework. The project contains some apis that serve some data. In public folder it contains index.html page having username and password field.
Index.html
<form action="/login" method="post">
<div>
<label>Username:</label>
<input type="text" name="name"/>
</div>
<div>
<label>Password:</label>
<input type="password" name="password"/>
</div>
<div>
<input type="submit" value="Log In"/>
</div>
</form>
Created a server.ts that create a http server and listen on some port and created apis using express framework.
Server.ts
let userList: User[] = [new User(1, "Sunil"), new User(2, "Sukhi")];
let app = express();
// passport library
let passport = require('passport');
let LocalStrategy = require('passport-local').Strategy;
// middlewares
app.use(express.static("public"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({ resave: false, saveUninitialized: true, secret: "secretKey123!!" }));
// passport middleware invoked on every request to ensure session contains passport.user object
app.use(passport.initialize());
// load seriliazed session user object to req.user
app.use(passport.session());
// Only during the authentication to specify what user information should be stored in the session.
passport.serializeUser(function (user, done) {
console.log("Serializer : ", user)
done(null, user.userId);
});
// Invoked on every request by passport.session
passport.deserializeUser(function (userId, done) {
let user = userList.filter(user => userId === user.userId);
console.log("D-serializer : ", user);
// only pass if user exist in the session
if (user.length) {
done(null, user[0]);
}
});
// passport strategy : Only invoked on the route which uses the passport.authenticate middleware.
passport.use(new LocalStrategy({
usernameField: 'name',
passwordField: 'password'
},
function (username, password, done) {
console.log("Strategy : Authenticating if user is valid :", username)
let user = userList.filter(user => username === user.userName)
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
return done(null, user);
}
));
app.post('/login', passport.authenticate('local', {
successRedirect: '/done',
failureRedirect: '/login'
}));
app.get('/done', function (req, res) {
console.log("Done")
res.send("done")
})
app.get('/login', function (req, res) {
console.log("login")
res.send("login")
})
// http server creation
let server = http.createServer(app);
server.listen(7000, () => {
console.log('Up and running on port 7000');
});
Now when I hit localhost:7000 it opens the login page and when I click submit with username from userList it returns the done otherwise login. This is fine.
Now every call goes through deserializeUser method.
The problem is when I call other URLs directly without hitting /login (authenticates the user) they also work fine and return data.
I was expecting that if the request is not authenticated all other calls will fail as deserializeUser is intercepting every request but in this case, no passport method is called.
Is this how it works? or I am missing something?

You need to add a middleware, for check if your user is authenticated:
isAuthenticated = (req, res, next) => {
if (req.isAuthenticated()) {
//if user is logged in, req.isAuthenticated() will return true
return next();
}
res.redirect('/login');
};
And you have to use that middleware like that:
//if user not authenticated, he will be redirect on /login
app.get('/done', isAuthenticated, (req, res) => {
res.send("done")
});

I was missing middleware to authenticate all subsequent requests. So I have created isAuthenticated method (thanks #Sombrero).
// request interceptor that will check user authentication
private static isAuthenticated = (req, res, next) => {
console.log("Authenticating :", req.originalUrl)
if (req.isAuthenticated()) {
return next();
}
res.redirect('/login');
};
and then in every request
app.get('/done', isAuthenticated, (req, res) => {
res.send("done")
});
but this was tough to use isAuthenticated method in every request. So I created an array of API list that is public and added middleware to intercept every request and updated isAuthenticated method to ignore public apis
// list of apis for which authentication is not required
private static publicApiList: string[] = ["/login"];
// request interceptor that will check user authentication
private static isAuthenticated = (req, res, next) => {
console.log("Authenticating :", req.originalUrl)
if (req.isAuthenticated() || Server.publicApiList.indexOf(req.originalUrl) > -1) {
return next();
}
res.redirect('/login');
};
and then used this method as middleware
app.use(Server.isAuthenticated)

Related

What's the difference between req.isAuthenticated() and passport.authenticate() in passport?

I am a beginner in programmation and experimenting the authentication process through node.js, express and mongoDB. I have used passport, passport-local and passport-local-mongoose to create a login/logout for users.
When my authentication succeed, user is redirect to my index page which show his/her name.
But I have a question… What is the difference between req.isAuthenticated() and passport.authenticate() ?
In my main.js, I have directly placed my req.user in the core of my session :
const passport = require('passport');
const expressSession =require('express-session');
const cookieParser = require('cookie-parser');
const connectFlash = require('connect-flash')
const localStrategy = require('passport-local').Strategy;
app.use(cookieParser("SecretStringForCookies"));
app.use(
expressSession({
secret : "SecretStringForCookies",
cookie : {
maxAge: 2000000
},
resave : false,
saveUninitialized : false
}))
app.use(passport.initialize());
app.use(passport.session());
//Serializing and deserializing user for checking login status in cookie
const User = require('./models/allUsers');
passport.use(User.createStrategy());
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
app.use(connectFlash());
app.use((req, res, next) => {
res.locals.flashMessages = req.flash();
res.locals.currentUser = req.user;
next();
});
For my authentication to succeed, I have used the following code in a UserController.js page :
module.exports = {
authentication : passport.authenticate("local", {
failureRedirect: "/login",
successRedirect: "/index",
successFlash : { type: 'success_msg', message: 'Welcome !' },
failureFlash : { type: 'error_msg', message: 'Your email and/or password are wrong, try again !' }
}),
isAuthenticatedUser : (req, res, next) => {
if(req.isAuthenticated()) {
next();
}
res.redirect = "/login";
},
}
My routes regarding the authentification :
const express = require('express');
const router = express.Router();
const userController = require('./userController');
router.post('/login', userController.authenticate, userController.isAuthenticatedUser);
router.get("/logout", userController.isAuthenticatedUser, (req, res)=> {req.logout(), res.redirect("/")});
router.get('/index');
My HTML :
<nav class="nav-links">
<% if(currentUser) { %>
<ul>
<li><%= currentUser.name %></li>
<li>Logout</li>
<li>Home</li>
</ul>
<% } %>
</nav>
However, my login authentication process seems to work fine with just only passport.authenticate() and my routes for login/logout doesn’t seem to need my function about req.isAuthenticated().
Sorry if my question seems dumb or weird but I am really confused about its purpose…
Could you please give me some advice ?
Thank you in advance for your help !
passport.authenticate() method extracts user credentials from request object and passes them to the authentication function which you use to authenticate the process,
passport.use(new LocalStrategy(
function(username, password, done) { // this is an authentication function
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (!user.verifyPassword(password)) { return done(null, false); }
return done(null, user);
});
}
));
By default, when authentication succeeds, the req.user property is set to the authenticated user, a session is established, and the next function in the stack is called.
req.isAuthenticated() method checks if the user is already authenticated by the authentication function or not, for example, if you have an admin dashboard page and you want to ensure that only authenticated users can access this page, therefore you use req.isAuthenticated() method to ensure that the user who sends the request is already authenticated by the authentication function.
module.exports= (req,res, next)=>{
if(req.isAuthenticated()){ //checks whether user
//is authenticated by the passport.authenticate() method
next();
}
res.redirect('/login');
}
The req.isAuthenticated() command returns whether the user is logged in or not, and the other ensures that the user is logged in.

PassportJS authenticates user but returns 401 Unauthorized on subsequent requests

I'm writing one of my first applications in NodeJS so please bear with me. I've managed to successfully authenticate a user to our Active directory and I can see the connect.sid cookie being set and used on the subsequent requests.
Upon debugging the application by dumping the req object I can also see that the user variable has been set successfully. From the documentation I've read that seems to be a criteria for a successful session match?
However, the request is still getting a 401 Unauthorized.
To summarize:
The user is successfully authenticated after posting credentials /login.
Upon successful authentication the user is redirected to "/".
The "/" path replies with 401 Unauthorized.
Any ideas much appreciated. Code below.
const express = require('express');
var bodyParser = require('body-parser');
var session = require('express-session');
var passport = require('passport')
var ActiveDirectoryStrategy = require('passport-activedirectory')
// Setup the authentication strategy
passport.use(new ActiveDirectoryStrategy({
integrated: false,
ldap: {
url: 'ldap://myad.company.com',
baseDN: 'DC=domain,DC=company,DC=com',
username: 'user',
password: 'password'
}
}, function (profile, ad, done) {
ad.isUserMemberOf(profile._json.dn, 'Group', function (err, isMember) {
if (err) return done(err)
return done(null, profile)
})
}));
passport.serializeUser(function(user, done) {
done(null, JSON.stringify(user));
});
passport.deserializeUser(function(user, done) {
done(null, JSON.parse(user));
});
const app = express();
app.use(bodyParser.urlencoded({extended: true}));
app.use(session(
{ secret: "password" }
));
app.use(passport.initialize());
app.use(passport.session());
// For debugging purposes
app.use(function (req, res, next) {
console.log(req)
next()
})
// The login page posts a form containing user and password
app.get("/login", (req, res) => {
res.sendFile(__dirname + '/public/index.html');
})
// Handler for the login page. Receives user and password and redirects the user to /
app.post('/login',
passport.authenticate('ActiveDirectory', {
failWithError: true,
successRedirect: "/",
failureRedirect: "/login"
}
), function(req, res) {
res.json(req.user)
}, function (err) {
res.status(401).send('Not Authenticated')
}
)
// This is where the issue happens. The page returns "Unauthorized".
// Using console.log(req) shows that the user property has been set to the req object.
// However, for some reason it still fails.
app.get('/',
passport.authenticate('ActiveDirectory', {
failWithError: true,
}
), function(req, res) {
res.send("test")
}, function (err) {
res.status(401).send('Not Authenticated')
})
Found what I did wrong!
The .authenticate method is only used to validate credentials, not to validate a session.
So this:
app.get('/',
passport.authenticate('ActiveDirectory', {
failWithError: true,
}
), function(req, res) {
res.send("test")
}, function (err) {
res.status(401).send('Not Authenticated')
})
Should become:
app.get('/', function(req, res, next) {
// This is verifying that the user part has been populated,
// which means that the user has been authenticated.
if (req.user) {
res.send('Returning with some text');
} else {
// If the user property does no exist, redirect to /login
res.redirect('/login');
}
});
Another thing that I changed was the serialize/deserialize functions:
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
This removes redundant serializing/deserializing.
These articles really helped me understand the flow:
http://toon.io/understanding-passportjs-authentication-flow/
https://www.airpair.com/express/posts/expressjs-and-passportjs-sessions-deep-dive
Hope it helps someone else!
/Patrik

How to flash a message from Passport.js?

I've created login and register with express and passport js.
I want to add a message for wrong password or email.
in my index.js (main) added passport and body parser middleware with referring to the routes :
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// route files
let index = require("./routes/index");
let auth = require("./routes/auth");
app.use("/", index);
app.use("/auth", auth);
and I created passport configuration :
const LocalStrategy = require("passport-local").Strategy;
const User = require("../models/User");
const config = require("../config/database");
const bcrypt = require("bcryptjs");
module.exports = function(passport) {
// Local Strategy
passport.use(
new LocalStrategy(
{
usernameField: "email",
passwordField: "password"
},
(username, password, done) => {
// Match Email
let query = { email: username };
User.findOne(query, function(err, user) {
if (err) throw err;
if (!user) {
return done(null, false, { message: "No user found" });
}
// Match Password
bcrypt.compare(password, user.password, function(err, isMatch) {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: "Wrong password" });
}
});
});
}
)
);
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
};
also added a route for it :
// Login Process
router.post("/login", (req, res, next) => {
passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/auth/login",
failureFlash: true
})(req, res, next);
});
the successRedirect and failureRedirect work fine, but it doesn't give me any error. I did it on a youtube video in the video it works but in my code it doesn't.
configuration of connect flash :
const flash = require("connect-flash");
app.use(require("connect-flash")());
Nothing wrong with your code, its just the version of express you are using. From the Passportjs Flash Message documentation,
Note: Using flash messages requires a req.flash() function. Express 2.x provided this functionality, however it was removed from Express 3.x. Use of connect-flash middleware is recommended to provide this functionality when using Express 3.x.
So you need to install connect-flash express middleware as it is recommended.
var flash = require('connect-flash');
var app = express();
app.configure(function() {
app.use(express.cookieParser('keyboard cat'));
app.use(express.session({ cookie: { maxAge: 60000 }}));
app.use(flash());
});
With the flash middleware in place, all requests will have a req.flash() function that can be used for flash messages.
app.get('/flash', function(req, res){
req.flash('info', 'Flash is back!')
res.redirect('/');
});
app.get('/', function(req, res){
res.render('index', { messages: req.flash('info') });
});
This might help you.
First, I don't quite see the point of configuring
app.use() with require. Just call the flash() method inside app.use() like this in your configuration.
var flash = require("connect-flash");
app.use(flash());
What you're missing is req.flash("error"). Because when a failure occurs passport passes the message object as error.
Passport Authenticate
Setting the failureFlash option to true instructs Passport to flash an
error message using the message given by the strategy's verify
callback, if any.
This code is working at my end and passing req.flash() messages.
routes.js
//route for passport strategy
router.post("/login", passport.authenticate("local-signin", {
failureRedirect: "/error",
failureFlash: true,
}));
//route for error page
router.get("/error", function(req, res, next) {
res.render("error", {
error: req.flash("error"),
});
});
On the view side now you have access to error object hence can use it for view.
In my error.hbs handlebars view I do this.
error.hbs
{{#if error}}
{{error}}
//Wrong password or No User Found
{{/if}}
<p>No results to show.</p>
Hope this helps.
Simpler route to display success / failure flash messages:
router.post('/login', passport.authenticate("local",
{
successRedirect: "/",
failureRedirect: "/auth/login",
successFlash: true,
failureFlash: true,
successFlash: 'Succesfu1!',
failureFlash: 'Invalid username or passwerd.'
})
);
To return message without using flash option, change your post method like this
router.post('/users/signin', function (req, res, next) {
passport.authenticate('local', function (err, user, info) {
if (err) {
return next(err);
}
if (!user) {
// *** Display message without using flash option
res.status(500).send({ message: info.message });
} else {
// *** Display message without using flash option
res.status(200).send({ message: 'success' });
}
})(req, res, next);
});

passport-github how to extract session cookie to know that the user already logged in

I am building a passport-github auth to my application. but I think currently I don't know how to extract the cookie from request that would say user is already logged in. so everytime When i go to home page i get redirected to /login.
My code roughly looks like this:
passport.use(new GitHubStrategy({
clientID: authConfig.GITHUB_CLIENT_ID,
clientSecret: authConfig.GITHUB_CLIENT_SECRET,
callbackURL: "http://127.0.0.1:8080/auth/github/callback"
},
function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
return db.user.findOne({where:{github_id:profile.id}})
.then(data=>{
if (data) {
return done(null,data);
} else {
return db.user.build({ github_id: profile.id }).save()
.then(()=>{
return db.user.findOne({where:{github_id:profile.id}})
})
.then(data=>{
return done(null,data);
})
}
});
}
));
// 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
passport.serializeUser(function(user, done) {
console.log("serialize>>>>>", user.github_id);
done(null, user.github_id);
});
passport.deserializeUser(function(id, done) {
console.log("deserialize>>>>", id);
db.user.findOne({where:{github_id: id}})
.then(user=>{
done(null, user.toJSON());
})
});
I have established the session :
app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
And I have an isAuthenticated function that checks for req info:
function isAuthenticated (req, res, next) {
// If the user is logged in, continue with the request to the restricted route
console.log("req.user is>>>>", req);
if (req.isAuthenticated()) {
return next();
}
// If the user isnt' logged in, redirect them to the login page
return res.redirect("/index");
}
I am using this passport-github lib. I cannot get some useful information from reqseems
updated to include routes:
Here is the routes:
const isAuthenticated = require('./middleware/isAuthenticated.js');
router
.get('/index', query.renderIndex)
.get('/', isAuthenticated, query.displayRepos)
.post('/', query.queryRepoTopic)
.post('/trending', query.addRepo)
.post('/addTopic', query.addTopic)
.get('trending', query.updateScore);
router.get('/login', auth.loginPage)
.get('/auth/github',
passport.authenticate('github', { scope: [ 'user:email' ] }),
function(req, res){}
)
.get('/auth/github/callback',
passport.authenticate('github', { failureRedirect: '/login' }),
auth.signInRedirect
)
.get('/logout', auth.logout);
Here is the controller function that does the logic:
const loginPage = (req, res) => {
res.render('index');
}
// signin a user in
const signInRedirect = (req, res) => {
console.log("here in callback>>>");
console.log("req.user is>>>>", req.user);
//res.json("you have successfully logged in!");
res.redirect('/');
}
const logout = (req, res) => {
req.logout();
res.redirect('/index');
}
I see you have this route configuration:
const isAuthenticated = require('./middleware/isAuthenticated.js');
router
.get('/index', query.renderIndex)
.get('/', isAuthenticated, query.displayRepos)
...
If you want to call localhost:3000, and be redirected to auth/github when you are not logged in, you could change isAuthenticated function like this:
function isAuthenticated (req, res, next) {
// If the user is logged in, continue with the request to the restricted route
console.log("req.user is>>>>", req);
if (req.isAuthenticated()) {
return next();
}
// If the user isnt' logged in, redirect them to the github login page.
return res.redirect("/auth/github");
}
Wich means, when you try to call the '/', the isAuthenticated will check if the req.user was set (if (req.isAuthenticated())), if not, redirect to the /auth/github route.
Have you tried this?
Have it can help!

Passport.js: how to access user object after authentication?

I'm using Passport.js to login a user with username and password. I'm essentially using the sample code from the Passport site. Here are the relevant parts (I think) of my code:
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
passport.use(new LocalStrategy(function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login/fail', failureFlash: false }),
function(req, res) {
// Successful login
//console.log("Login successful.");
// I CAN ACCESS req.user here
});
This seems to login correctly. However, I would like to be able to access the login user's information in other parts of the code, such as:
app.get('/test', function(req, res){
// How can I get the user's login info here?
console.log(req.user); // <------ this outputs undefined
});
I have checked other questions on SO, but I'm not sure what I'm doing wrong here. Thank you!
Late to the party but found this unanswered after googling the answer myself.
Inside the request will be a req.user object that you can work withr.
Routes like so:
app.get('/api/portfolio', passport.authenticate('jwt', { session: false }), stocks.buy);
Controller like this:
buy: function(req, res) {
console.log(req.body);
//res.json({lel: req.user._id});
res.json({lel: req.user});
}
In reference to the Passport documentation, the user object is contained in req.user. See below.
app.post('/login',
passport.authenticate('local'),function(req, res) {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
res.redirect('/users/' + req.user.username);
});
That way, you can access your user object from the page you redirect to.
In case you get stuck, you can refer to my Github project where I implemented it clearly.
I'm pretty new to javascript but as I understand it from the tutorials you have to implement some session middleware first as indicated by 250R.
const session = require('express-session')
const app = express()
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
let sess = {
genid: (req) => {
console.log('Inside the session middleware')
console.log(req.sessionID)
return uuid()
},
store: new FileStore(),
secret: 'keyboard cat', // password from environment
resave: false,
rolling: true,
saveUninitialized: true,
cookie: {
HttpOnly: true,
maxAge: 30 * 60 * 1000 // 30 minutes
}
}
app.use(session(sess))
// call passport after configuring the session with express-session
// as it rides on top of it
app.use(passport.initialize())
app.use(passport.session())
// then you will be able to use the 'user' property on the `req` object
// containing all your session details
app.get('/test', function (req, res) {
console.log(req.user)
})
res.render accepts an optional parameter that is an object containing local variables for the view.
If you use passport and already authenticated the user then req.user contains the authenticated user.
// app.js
app.get('/dashboard', (req, res) => {
res.render('./dashboard', { user: req.user })
})
// index.ejs
<%= user.name %>
You can define your route this way as follows.
router.post('/login',
passport.authenticate('local' , {failureRedirect:'/login', failureFlash: true}),
function(req, res) {
res.redirect('/home?' + req.user.username);
});
In the above code snippet, you can access and pass any field of the user object as "req.user.field_name" to the page you want to redirect. One thing to note here is that the base url of the page you want to redirect to should be followed by a question mark.
late to party but this worked for me
use this in your app.js
app.use(function(req,res,next){
res.locals.currentUser = req.user;
next();
})
get current user details in client side like ejs
<%= locals.currentUser.[parameter like name || email] %>
Solution for those using Next.js:
Oddly, —> the solution <— comes from a recently removed part of the README of next-connect, but works just as it should. You can ignore the typescript parts if you're using plain JS.
The key part is the getServerSideProps function in ./src/pages/index (or whichever file you want to get the user object for).
// —> ./src/authMiddleware.ts
// You'll need your session, initialised passport and passport with the session,
// so here's an example of how we've got ours setup, yours may be different
//
// Create the Passport middleware for SAML auth.
//
export const ppinit = passport.initialize();
//
// Set up Passport to work with expressjs sessions.
//
export const ppsession = passport.session();
//
// Set up expressjs session handling middleware
//
export const sess = session({
secret: process.env.sessionSecret as string,
resave: true,
saveUninitialized: true,
store: sessionStore,
});
// —> ./src/pages/index.ts
// update your user interface to match yours
export interface User {
id: string;
name: string;
}
interface ExtendedReq extends NextApiRequest {
user: User;
}
interface ServerProps {
req: ExtendedReq;
res: NextApiResponse;
}
interface ServerPropsReturn {
user?: User;
}
export async function getServerSideProps({ req, res }: ServerProps) {
const middleware = nc()
.use(sess, ppinit, ppsession)
.get((req: Express.Request, res: NextApiResponse, next) => {
next();
});
try {
await middleware.run(req, res);
} catch (e) {
// handle the error
}
const props: ServerPropsReturn = {};
if (req.user) props.user = req.user;
return { props };
}
interface Props {
user?: User;
}
//
// A trivial Home page - it should show minimal info if the user is not authenticated.
//
export default function Home({ user }: Props) {
return (
<>
<Head>
<title>My app</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<h1>Welcome to My App {user?.name}</h1>
</main>
</>
);
}
You'll need to make sure that you register a middleware that populates req.session before registering the passport middlewares.
For example the following uses express cookieSession middleware
app.configure(function() {
// some code ...
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.cookieSession()); // Express cookie session middleware
app.use(passport.initialize()); // passport initialize middleware
app.use(passport.session()); // passport session middleware
// more code ...
});

Resources