Passport authentication works when login, but fails when register - javascript

I am learning passport.js and session, and am trying to add a local login feature to my website.
What I was trying to do is as follows:
Secret page: When users are authenticated, they can access to the secret page, otherwise they will be transfered to the login page.
Login page: If the username and password match, users are authenticated and are transfered to the secret page, otherwise they would be transfered back to the login page.
Register page: Users provide username and password, a new MongoDB document is created and stored in my database, meanwhile, the user is authenticated in this session and are transfered to the secret page.
My Problem:
Login page works fine: The authentication process in the login page works perfectly fine. When logined, a cookie is sent to the browser, the user is authenticated in this session and is successfully accessed to the secret page.
Register page data insert works fine: After register, the new password and username is successfully saved in the MongoDB database. User could login with this newly registered username.
Register page authentication failed: The register page, although using the same passport.authenticate() function, failed in the authentication process. I didn't get any error messages on my console, either.
I searched on Google and tried several methods of passport authentication, and they all worked very well on the 'login' POST Request, but failed on the 'register' POST Request.
I have read the documentation of passport-local and passport-local-mongoose. I have also tried this solution. But they all failed in the authentication process in the register page.
I use express-session, passport, passport-local-mongoose packages.
I'm wondering if my understanding of passport authentication is still lacking?
Thank you so much for your help and patience!
My Code:
EJS File - Register Page:
<!-- ... -->
<form action="/register" method="POST">
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control" name="username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" name="password">
</div>
<button type="submit" class="btn btn-dark">Register</button>
</form>
<!-- ... -->
JS File:
require('dotenv').config();
const express = require('express');
const app = express();
const port = 3000;
const ejs = require('ejs');
const session = require('express-session');
const passport = require('passport');
const passportLocalMongoose = require('passport-local-mongoose')
app.use(express.urlencoded({
extended: true
}));
app.use(express.static("public"));
app.set("view engine", "ejs");
//session and passport set up
app.use(session({
secret: process.env.SECRET,
resave: false,
saveUninitialized: true,
cookie: {
sameSite: 'lax'
},
}))
app.use(passport.initialize());
app.use(passport.session());
//user database set up ////////
const mongoose = require("mongoose");
main().catch(err => console.log(err));
async function main() {
await mongoose.connect('mongodb://localhost:27017/userDB');
}
const userSchema = new mongoose.Schema({
username: {
type: String,
unique: true
},
password: String
})
userSchema.plugin(passportLocalMongoose)
const User = mongoose.model("User", userSchema)
passport.use(User.createStrategy());
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
//user database set up end ///////
//GET Request
app.get("/", function(req, res) {
res.render("home");
})
app.get("/login", function(req, res) {
res.render("login");
})
app.get("/register", function(req, res) {
res.render("register");
})
//Secret Page ////////////////////////////////
app.get("/secrets", function(req, res) {
if (req.isAuthenticated()) {
res.render("secrets")
} else(
res.redirect("/login")
)
})
app.get('/logout', function(req, res) {
req.logout(function(err) {
if (err) {
console.log(err);
}
res.redirect('/');
});
});
//POST request
//Register POST Request ////////////////////////////////
app.post("/register", function(req, res) {
User.register(new User({
username: req.body.username
}), req.body.password,
function(err, user) {
if (err) {
console.log(err);
res.redirect('/register')
}
passport.authenticate('local'), function(req, res) {
res.redirect("/secrets");
}
}
)
})
app.post('/login', passport.authenticate('local', {
failureRedirect: '/login',
}), function(req, res) {
res.redirect("/secrets");
});
app.listen(port, function() {
console.log("start listening to port 3000")
})

I tried this answer by adding a parameter 'returnCode' to the call back of the passport authenticate function. Although the return code logged is 'undefined', the code successfully worked! I still don't know precisely why it worked simply by adding a parameter, but I think that's due to my lack of understanding of passport?
One thing I found very unhelpful is that the example page on the passport-local documentation is 404 not found. I wonder if there are any helpful examples I could find to study passport-local?
Here's my Code
app.post("/register", function(req, res) {
User.register(new User({
username: req.body.username
}), req.body.password,
function(err, user) {
if (err) {
console.log(err);
res.redirect('/register')
} else {
passport.authenticate('local', {})(req, res, function(returnCode) {
console.log(returnCode); //return: undefined
res.redirect('/secrets');
})
}
}
)
})

Related

Allowing users to use either email or username for login using Passport, LocalStrategy

I am relatively new to web development, and I am creating a website with NodeJs and Express that would ask for a username and email when registering. Currently, it only accepts username for login. I am using Passport and LocalStrategy. I want users to be able to provide either the username or email in the same input field, How can I do that? I have searched the internet and found similar questions, but I haven't been able to implement their answers successfully. Here are the relevant parts of my app.js
var express = require("express"),
mongoose = require("mongoose"),
passport = require("passport"),
LocalStrategy = require("passport-local"),
User = require("./models/user");
// PASSPORT CONFIGURATION
app.use(require("express-session")({
secret: "Once again Rusty wins cutest dog!",
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
// Register
app.get("/register", function(req, res) {
res.render("register");
});
app.post("/register", function(req, res){
var newUser = new User({email: req.body.email, username: req.body.username});
console.log(newUser);
User.register(newUser, req.body.password, function(err, user){
if(err) {
console.log(err);
return res.render("register");
}
passport.authenticate("local")(req, res, function(){
res.redirect("/");
});
});
});
// Login
app.get("/login" , function(req, res) {
res.render("login");
});
app.post("/login", passport.authenticate("local",
{
successRedirect: "/campgrounds",
failureRedirect: "/login"
}), function(req, res) {
});
This is my user model
var mongoose = require("mongoose");
var passportLocalMongoose = require("passport-local-mongoose");
var UserSchema = new mongoose.Schema({
email: String,
username: String,
password: String
});
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", UserSchema);
Thanks!
I was diving deeper into the docs of Passport Local Mongoose and found a way to do this. I'll leave the answer here in case someone finds it useful. When doing the plugin to the user model one can specify options:
User.plugin(passportLocalMongoose, options);
With the option usernameQueryFields one can provide other fields to look for the username (eg. email). So I changed my user.js like this:
UserSchema.plugin(passportLocalMongoose, {usernameQueryFields: ["email"]});
And now it users can provide either email or username in the login form

Invalid session token with parse-server, node.js, and express

I am trying to log in a user from a web site. I am using parse-server hosted at Microsoft Azure. I keep getting the following error, just trying to access the home page:
Error handling request: ParseError { code: 209, message: 'invalid session token' } code=209, message=invalid session token
And the browser throws a "...redirected you too many times." error. I'm not sure what I'm doing wrong, I've tried researching and piecing this together from here: https://github.com/ParsePlatform/parse-server/issues/497 with no luck.
index.js
var express...
etc...
var jsonParser = bodyParser.json();
var urlencodedParser = bodyParser.urlencoded({extended:false});
//Server configuration
...
//Express configuration
var app = express();
app.use(cookieParser()); // read cookies (needed for auth)
// get information from html forms
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());
// app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use('/public', express.static(__dirname + '/public'));
app.use('/parse', new ParseServer(config.server));
app.use('/parse-dashboard', ParseDashboard(config.dashboard, true));
app.use(cookieSession({
name: "COOKIE_NAME",
secret: "COOKIE_SECRET",
maxAge: 15724800000
}));
app.use(function (req, res, next) {
Parse.Cloud.httpRequest({
url: 'https://localhost:1337/parse/users/me',
headers: {
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-REST-API-Key': process.env.API_KEY,
'X-Parse-Session-Token': req.session.token
}
}).then(function (userData) {
req.user = Parse.Object.fromJSON(userData.data);
next();
}).then(null, function () {
return res.redirect('/login');
});
});
app.use(flash()); // use connect-flash for flash messages stored in session
//routes
require('./routes/routes.js')(app);
app.listen(process.env.PORT || url.parse(config.server.serverURL).port, function () {
console.log(`Parse Server running at ${config.server.serverURL}`);
});
routes.js
// app/routes.js
var bodyParser = require('body-parser');
var jsonParser = bodyParser.json();
var urlencodedParser = bodyParser.urlencoded({extended:false});
module.exports = function(app) {
// HOME PAGE ========
app.get('/', function(req, res) {
res.render('index.ejs', { title: 'Audiomesh' }); // load the index.ejs file
});
// LOGIN ===============================
// show the login form
app.get('/login', function(req, res) {
res.render('login.ejs', { message: req.flash('loginMessage') });
});
app.post('/login', function(req, res) {
Parse.User.logIn(req.body.username, req.body.password).then(function(user) {
req.session.user = user;
req.session.token = user.getSessionToken();
res.redirect('/dashboard');
}, function(error) {
req.session = null;
res.render('login', { flash: error.message });
});
});
// DASHBOARD =====================
app.get('/dashboard', function(req, res) {
res.render('dashboard.ejs', {
user : req.user // get the user out of session and pass to template
});
});
// LOGOUT ==============================
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
};
login.ejs
<body>
<p><%= message %></p>
<form name="loginForm" action="/login" method="post">
<label>Email</label>
<input type="email" name="email"></input>
<label>Password</label>
<input name="password" type="password"></input>
<input class="button" type="submit" value="Log In">
</form>
<p>Coming soon azure</p>
<p>Back to home page</p>
</body>
The goal of my web site is for the home page to be an advertising/landing page for the mobile app. So if you're logged in, there's no evidence here. Once you click "Login" then it would check if the user is logged in and either load their dashboard (if true), or the login page (if false).
The problem right now is I can't even load the home page. I get too many redirects.

Trouble with authentication

I´m facing a confusion issue while implementing the authentication for my restful api using passport local strategy.
Note:
I got the authentication working successfully when I´m doing it all in my index.js. But I want to use in Classes for better Code separation.
I have a passport.js Module
// config/passport.js
// load all the things we need
var LocalStrategy = require('passport-local').Strategy;
// load up the user model
var mysql = require('mysql');
var dbconfig = require('./database');
var connection = mysql.createConnection(dbconfig.connection);
module.exports = function(passport) {
// passport needs ability to serialize and unserialize users out of session
passport.serializeUser(function (user, done) {
//console.log("SER");
console.log(user),
done(null, user);
});
passport.deserializeUser(function (user, done) {
console.log("XXXX");
console.log(user);
connection.query("SELECT * FROM users WHERE name = ? ",user.name, function(err, rows){
console.log("DER");
console.log(rows);
done(err, rows[0]);
});
});
// passport local strategy for local-login, local refers to this app
passport.use('local-login', new LocalStrategy(
function (username, password, done) {
console.log("hhh");
console.log(username);
connection.query("SELECT * FROM users WHERE name = ? ",username, function(err, rows){
console.log(rows);
return done(err, rows[0]);
});
})
);
// route middleware to ensure user is logged in
function isLoggedIn(req, res, next) {
if (req.isAuthenticated())
return next();
res.sendStatus(401);
}
};
This is my Controller Class:
class AuthenticateController {
constructor(router, passport) {
this.router = router;
this.registerRoutes();
this.passport = passport;
}
registerRoutes() {
this.router.post('/login/:username/:password', this.login.bind(this));
//this.router.get('/logout', this.logout.bind(this));
this.router.get('/content', this.content.bind(this));
}
login(req, res) {
this.passport.authenticate("local-login", { failureRedirect: "/login"}),
res.redirect("/content");
}
content(req, res ) {
console.log(req.user);
if (req.isAuthenticated()) {
res.send("Congratulations! you've successfully logged in.")
} else {
res.sendStatus(401);
}
}
isLoggedIn(req, res, next) {
console.log(req.user);
if (req.isAuthenticated())
return next();
res.sendStatus(401);
}
}
module.exports = AuthenticateController;
The Controller gets the router and passport fully configured as parameters from my index.js.
//index.js
var express = require('express')
, cors = require('cors')
, app = express()
, passport = require('passport')
, morgan = require('morgan');
require('./config/passport')(passport); // pass passport for configuration
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(require('express-session')({secret: 'vidyapathaisalwaysrunning',
resave: true,
saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
app.use(cors());
var apiRouter = express.Router();
app.use('/api', apiRouter);
//
var apiV1 = express.Router();
apiRouter.use('/v1', apiV1);
var authenticateApiV1 = express.Router();
apiV1.use('/auth', authenticateApiV1);
var AuthenticateController = require('./controllers/authenticate');
var ac = new AuthenticateController(authenticateApiV1, passport); //pass in our fully configured passport
//If I call this /login instead of the /auth/login/ in the Controller Class it works!
//app.post("/login",
// passport.authenticate("local-login", { failureRedirect: "/login"}),
// function (req, res) {
// res.redirect("/content");
// });
What is working and what is not working
The Authentication in general is working. In my posted index.js you see app.post("/login", .... If I call this one the authentication is successfully and if I try to reach the restricted content in /auth/content/ req.user has a value (the user object) and I can successfully call req.isAuthenticated() .
BUT, If I use the authentication from /auth/login/username/password the req.user is undefined when trying to reach the restricted Content.
I get no error and the response of /auth/login/username/password/ HTTP Code 301 - 'redirecting to /content.
I have currently no idea what I´m doing wrong here and I´m pretty new to the topic of Node/express/ passport ..
Hope someone has an Idea. If you need something else to help me, just mention it in the comments and I will do my best to provide you everything you need.
Thanks
EDIT:
I recently tried to read the req.user in the login function and even there it is undefined
login(req, res) {
this.passport.authenticate("local-login", { failureRedirect: "/login"}),
console.log(req.user) //undefined
res.redirect("/content");
}
I guess it could be some async problem and I should use some callback functions, but I don´t know how to apply this in my login()
EDIT 2:
Another Issue I´m facing is the integration of the isLoggedIn() request.
If I do this:
registerRoutes() {
this.router.get('/', this.isLoggedIn, this.getUsers.bind(this));
this.router.get('/:id', this.getSingleUser.bind(this));
}
it results in 401 - Unauthorized
A console.log(req.user); in the isLoggedIn() results in undefined.
But if I call the first route without calling isLoggedIn() and do console.log(req.user); the user object exists.
The correct use of callback with passport authentication for local strategy can be as below:
function(req, res, next){
passport.authenticate('local-login', function(err, user, info){
if(err)
return logger.log('error', err);
if(user)
req.login(user, function(err){
if(err) return next(err);
return res.json({'success': true});
});
if(!user)
return res.json({'error':true, 'message': info.message, 'type': info.type});
})(req, res, next);
}
Please note the use of req.login() to explicitly set user in session.
The only thing I'm finding strange is that your route declarations are different.
In the AuthenticateController the route is declared as:
this.router.post('/login/:username/:password', ...
While in index.js, the route is simply declared as
app.post("/login", ...
How is your client submitting the login credentials to the server? If it is by form, like the tutorial, could it be that having :username and :password declared as route params but being sent by form messes with passport?
Try registering the route exactly like index.js
this.router.post('/login', ...
EDIT:
I've found another dicrepancy. In AuthenticateController the res.redirect("/content"); is not wrapped inside a callback. So it is being executed before Authenticate finishes running.
In the index.js example, passport is being used as a route middleware:
app.post("/login",
passport.authenticate("local-login", { failureRedirect: "/login"}),
function (req, res) {
res.redirect("/content");
});
While in the passport.js it is inside the callback. Consider declaring it in the route:
registerRoutes() {
this.router.post('/login', this.passport.authenticate("local-login", { failureRedirect: "/login"}), this.login.bind(this));
(...)
}
login(req, res) {
res.redirect("/content");
}
O, better yet, why not use passport's option to declare both success and failure redirects, since that seems to be all that you are doing:
login(req, res) {
this.passport.authenticate("local-login", { successRedirect: "/content", failureRedirect: "/login" });
}
You are passing this.login.bind(this) as a middleware to this.router.post('/login/:username/:password', this.login.bind(this)); but login(req, res) only responds to the request with res.redirect("/content"); i.e. redirecting to /content
So like you said, you need to supply a callback that does something with the user that is returned from passports middleware verify callback.
app.post("/login",
passport.authenticate("local-login", { failureRedirect: "/login"}),
function (req, res) {
console.log(req.user); // log user in console
res.json({user: req.user}); // send user as json response
});
The custom callback mentioned by #divsingh is if you want to explicitly have control of setting the session, error messages and redirecting the request. Any other information can be found under http://passportjs.org/docs

Cannot read property 'body' of null + passport.js

I'm trying to implement passport.js in a Node/Express/Sequelize app. I'm trying to stay as faithful as possible to both the official documentation and the Scotch.io tutorial, and currently have the following relevant code segments in my scaffolding:
app.js
const express = require('express');
const http = require('http');
const https = require('https');
const sequelize = require('sequelize');
const db = require('./config/sequelize');
const config = require('./config/config');
const passport = require('./config/passport');
const app = express();
const port = 3000;
app.use(passport.initialize());
app.use(passport.session());
app.set('view engine', 'ejs');
app.listen(port);
app.post('/signup', passport.authenticate('local', function(req, res, next, err){
if (err) { console.log(err); }
res.json(req.body);
}));
./config/passport.js
const db = require('./sequelize');
const passport = require('passport'),
LocalStrategy = require('passport-local').Strategy;
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(user, done) {
db.User.findById(id, function(err, user) {
done(err, user);
});
});
passport.use('local', new LocalStrategy({
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true
},
function(email, password, done){
process.nextTick(function() {
db.User.findOne({ email : 'local.email' }, function(err, user) {
if (err)
return done(err);
if (user) {
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
}
else {
db.User.create({
username: 'local.email',
password: 'local.password'
});
}
});
});
}
));
module.exports = passport;
./views/signup.ejs
<!-- LOGIN FORM -->
<form action="/signup" method="post">
<div class="form-group">
<label>Email</label>
<input type="text" name="username">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password">
</div>
<button type="submit">Signup</button>
</form>
With this implementation in hand, I fire up the app, enter the email address and password to sign up on ./views/signup.ejs, and get the following error:
TypeError: Cannot read property 'body' of null
at C:\Users\Adam\Desktop\repos\chinese-democracy\app.js:45:15
at allFailed (C:\Users\Adam\Desktop\repos\chinese-democracy\node_modules\passport\lib\middleware\authenticate.js:94:18)
at attempt (C:\Users\Adam\Desktop\repos\chinese-democracy\node_modules\passport\lib\middleware\authenticate.js:167:28)
...
This indicates that req.body is being returned as null, and I have a suspicion it has to do with the manner in which the deserializeUser and serializeUser functions are defined in ./config/passport.js, but how can I get verbose error outputs to determine what the exact cause is? Also, I have done sanity CRUD checks with my Sequelize database so I omitted that file, but will provide it as an edit if any of you think that would be of use in resolving this issue.
Please try this in your app.js :
app.post('/signup', passport.authenticate('local'), function(req, res, next, err){
//^ close
// If this function gets called, authentication was successful.
res.redirect('/users/' + req.user.username);
});
If you need a custom callback:
app.post('/signup', function(req, res, next){
passport.authenticate('local', function(err, user, info) {
//Your code here
})(req, res, next);
});
Please refer to http://passportjs.org/docs/authenticate for more details.

Get POST request when using Router middleware

I'm using Passport to authenticate my users on NodeJS. Currently I'm using ExpressJS and I'm trying to route my traffic. I currently use the following code:
website.js (main file)
require("./routes.js")(app);
routes.js
var pages = {
home: require("./pages/home"),
about: require("./pages/about"),
register: require("./pages/register"),
login: require("./pages/login"),
api: require("./api/index")
};
module.exports = function(app) {
app.use("/", pages['home']);
for (page in pages) {
app.use("/" + page, pages[page]);
}
app.get("/logout", function(req, res) {
req.logout();
req.redirect("/");
});
}
register.js
var express = require('express');
var router = express.Router();
var app = express();
router.get("/", function(req, res) {
res.render("register", { page: "Register", message: req.flash("registerMessage") });
});
app.post("/", passport.authenticate("register", {
successRedirect: "/about/",
failureRedirect: "/register/",
failureFlash: true,
successFlash: "Logged in!"
}));
module.exports = router;
The problem I am facing is that POST requests to this will result in a 404. The page is not found. The GET request (so /register) properly shows the registration form, but upon submitting I get a 404. If I change router.get("/", function(req,res){}) to router.use("/", function(req, res, next) {}), I will get HTTP 500 errors when I call "Next()" (Can't set headers after they are sent.), and POST still doesn't work.
Could anyone tell me how to correctly catch POST requests behind router middleware?
I solved my issue
I solved my issue by using the following:
router.route("/")
.get(function(req, res, next) {
res.render("register", { page: "Register", message: req.flash("registerMessage") });
})
.post(passport.authenticate("register", {
successRedirect: "/about/",
failureRedirect: "/register/",
failureFlash: true,
successFlash: "Logged in!"
}));

Categories

Resources