I've seen similar questions to mine, but I cannot find the right answer.
I'm trying to implement a very simple login form using nodejs+express+passport (local strategy). The thing is, it seems that my authenticate callback always fails. I've removed every connection to the test DB (every tutorial has an example MongoDB).
This is my login form
<html>
<body>
<form action="/login" method="post">
<div>
<label>Username:</label>
<input type="text" name="username" />
<br/>
</div>
<div>
<label>Password:</label>
<input type="password" name="password" />
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
</body>
</html>
This is my server.js (i run it with npm start)
var express = require('express');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var app = express();
var port = process.env.PORT || 8080;
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(function(username, password, done) {
// no authentication logic here... just return done with an object with 2 fields
return (done, {username : username, password : password});
}));
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
// ROUTES
// ==============================================
// sample route with a route the way we're used to seeing it
app.post('/login',
passport.authenticate('local', {
successRedirect: '/loginSuccess',
failureRedirect: '/loginFailure'
})
);
app.get('/login', function(req, res) {
res.sendfile('views/login.html');
});
app.get('/loginFailure', function(req, res, next) {
res.send('Failed to authenticate');
});
app.get('/loginSuccess', function(req, res, next) {
res.send('Successfully authenticated');
});
// START THE SERVER
// ==============================================
app.listen(port);
console.log('Magic happens on port ' + port);
I always get the Failed to authenticate message. Like I said, I've stripped every code relating to a schema/model with mongoose to read data from a sample MongoDB. The passport.authenticate callback just returns the done function with an object with 2 fields, called username and password like the ones expected by passport-local
Can you help me?
I don't believe you're invoking the callback correctly:
First param expects an error or null and second expects false, or user object.
passport.use(new LocalStrategy(function(username, password, done) {
// no authentication logic here... just return done with an object with 2 fields
done(null, {username : username, password : password});
}));
or, using a db:
passport.use(new LocalStrategy(function(username, password, done) {
db.users.findOne({ username : username}, function(err, user){
if(err) return done(err);
if(!user || user.password !== password) return done(null, false);
done(null, user);
});
});
Here you can see we compare passwords before responding with the user object, which then gets attached by the passport lib to req.user. Ofcourse you wouldn't compare the two passwords but instead their hashes using bcrypt or similar.
Related
I have no problems registering but when trying to log in all the that happens is the page refreshes with no error messages.
Here is the login form (I'm using ejs)
<form class="" action="/login" method="post">
<h1>Login</h1>
<div class="EmailAddress">
<input type="text" name="email" placeholder="Email">
</div>
<div class="Password">
<input type="password" name="password" placeholder="Password">
</div>
<div class="Login">
<input type="submit" value="submit">
</div>
</form>
I have created two models for two different types of user one is a Carrier and one is a Customer and they are both practically identical and working perfectly fine for registering. For the moment I'm only trying to log in a Customer so here is here my passport.js file:
Edit: I've updated it according to what zerosand1s said below and still not working unfortunately.
module.exports = function(passport) {
//LocalStrategy
passport.use(
new LocalStrategy(
{ passReqToCallback: true },
{
usernameField: "email",
passwordField: "password"
},
function(req, email, password, done) {
let query = { email: email };
// checking the name in the database against the one submitted on the form
Customer.findOne(query, function(err, customer) {
if (err) throw err;
if (!customer) {
return done(null, false, {
message: "No such password",
type: "error"
});
}
//Match password
bcrypt.compare(password, customer.password, function(err, isMatch) {
if (err) throw err;
if (isMatch) {
return done(null, customer);
} else {
return done(null, false, {
message: "No such password",
type: "error"
});
// if the password isnt an matc return wrong password
}
});
});
}
)
);
};
passport.serializeUser(function(customer, done) {
done(null, customer.id);
});
passport.deserializeUser(function(id, done) {
Customer.findById(id, function(err, customer) {
done(err, customer);
});
});
Heres my routes file for logging in
index.js
// login route
router.get("/login", function(req, res) {
res.render("login", {message: req.flash('error')});
});
// login Process
router.post("/login", function(req, res, next) {
passport.authenticate('local', {
// were using the LocalStrategy used in config/passport file
successRedirect:'../customer/dashboard',
failureRedirect:'/login',
failureFlash: true
})(req, res, next);
});
router.get("/customer/dashboard", function(req, res) {
res.render("customer/dashboard");
});
And the only place I can think there might be a problem is my app.js file:
// Express session
app.use(session({
secret: 'keyboard cat',
resave: true,
saveUninitialized: true,
cookie: { secure: true }
}));
// Express messages middleware
app.use(require('connect-flash')());
app.use(function (req, res, next) {
res.locals.messages = require('express-messages')(req, res);
// setting a global variable called express-messages
next();
});
// Express validator middleware
app.use(expressValidator({
errorFormatter: function(param, msg, value) {
var namespace = param.split('.')
, root = namespace.shift()
, formParam = root;
while(namespace.length) {
formParam += '[' + namespace.shift() + ']';
}
return {
param : formParam,
msg : msg,
value : value
};
}
}));
app.use(function (req, res, next) {
res.locals.messages = require('express-messages')(req, res);
next();
});
// Passport Config
require('./config/passport')(passport);
// Passport Middleware
app.use(passport.initialize());
app.use(passport.session());
app.get('*', function(req, res, next) {
res.locals.customer = req.customer || null;
next();
});
app.use(cookieparser());
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.listen(3000, function() {
console.log('server is running');
});
app.use(flash());
app.use(routes);
I've spent the entire day ripping my hair other this so any help is greatly appreciated
Since you are using email for login (instead of default username), you need to specify as such in your Passport local strategy.
From the Passport documentation,
By default, LocalStrategy expects to find credentials in parameters
named username and password. If your site prefers to name these fields
differently, options are available to change the defaults.
passport.use(new LocalStrategy({
passReqToCallback: true
usernameField: 'email',
passwordField: 'passwd'
},
function(username, password, done) {
// ...
}
));
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)
I'm trying to setup passport local sign-in but the form doesn't do anything when submitted.
My app.js code:
var express = require('express');
var app = express();
app.use(express.static(__dirname + '/public'));
var credentials = require('./credentials.js');
app.use(require('cookie-parser')(credentials.cookieSecret));
app.use(require('body-parser').urlencoded({extended: false}));
var session = require('express-session');
app.use(session({
resave: false,
saveUninitialized: true,
secret: 'keyboard cat'
}));
var passport = require('passport');
app.use(passport.initialize());
app.use(passport.session());
app.disable('x-powered-by');
var handlebars = require('express-handlebars').create({defaultLayout:'main'});
app.engine('handlebars', handlebars.engine);
app.set('view engine', 'handlebars');
app.set('port', process.env.PORT || 3000);
app.post('/login', function(req, res){console.log("body parsing", req.body)});
app.get('/login', function(req, res) {
res.render('login');
});
The form html at /login for sign-ins:
<form action="/login" method="post">
<div>
<label>Username:</label>
<input type="text" name="username"/>
</div>
<div>
<label>Password:</label>
<input type="password" name="password"/>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
When I fill in username and password nothing prints out into the console from the post request. What do I have wrong?
You have to serialize and deserialize the user details to make the user authenticated.
Here's and example.
services.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/user');
//serialize and deserialize
passport.serializeUser(function(user, done) {
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
//middleware
passport.use('local-login', new LocalStrategy({
usernameField: 'email',
password: 'password',
passReqToCallback: true
}, (req, email, password, done) => {
User.findOne({ email: email.toLowerCase() }, function(err, user) {
if (err) return done(err);
if (!user) {
return done(null, false, req.flash('loginMessage', 'No user has been found'));
}
if (!user.comparePassword(password)) {
return done(null, false, req.flash('loginMessage', 'Oops! Wrong Password.'));
}
return done(null, user);
});
}));
//custom function to validate
exports.isAuthenticated = function(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.redirect('/login');
}
and once you do the serializing, you have to actually apply the process to the router. i.e, make the user details enter passport.
In your router file:
const passportConfig = require('services.js');
Then you can simply access the data via req.user
I found that removing this AMP js script solves the issue. For some reason it breaks the post requests.
<script async src="https://cdn.ampproject.org/v0.js"></script>
I want to create session for my pages, when give a url like http://localhost:3000/pages/profile it will goes to that page without logged in. What should i do now to fix this problem.
node.js
module.exports = function(app, express, passport){
var router = express.Router();
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'},
function(username, password, done) {
User.findOne({ name : username}, function(err, user) {
if (!user){
return done(null, false,{message: 'Incorrect username' });
}
if(user){
var validPassword = user.comparePassword(password);
if(!validPassword){
return done(null, false,{message: 'Incorrect password' });
}
}
return done(null, user);
});
}
));
router.post('/pages/auth/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (user === false) {
console.log("login error ");
return res.json({
success:false,
message: info.message,
});
} else {
console.log("login success");
return res.json({
success:true,
//message: 'Login Success',
});
}
})(req, res, next);
});
}
controller
function LoginController($http, $location, Auth, $rootScope)
{
var vm = this;
vm.submitPost = function(userData){
$http({
url: 'http://localhost:7200/api/pages/auth/login',
method: 'POST',
data: userData
}).then(function(res) {
if(res.data.success){
$location.path('/pages/profile');
} else {
vm.message=res.data.message;
$location.path('/pages/auth/login');
}
}, function(error) {
console.log(error);
alert(error.data);
});
};
}
login.html
<form name="loginForm">
<div class="alertmessage" >{{vm.message}}</div>
<md-input-container flex md-no-float>
<input ng-model="vm.form.username" placeholder="Username" translate
translate-attr-placeholder="LOGIN.USERNAME" name="username" required="true">
<div ng-messages="loginForm.username.$error" ng-show="loginForm.username.$touched">
<div ng-message="required">This field is required</div>
</div>
</md-input-container>
<md-input-container flex md-no-float>
<input ng-model="vm.form.password" type="password" placeholder="Password" translate
translate-attr-placeholder="LOGIN.PASSWORD" name="password" required="true">
<div ng-messages="loginForm.password.$error" ng-show="loginForm.password.$touched">
<div ng-message="required">This field is required</div>
</div>
</md-input-container>
<div class="remember-forgot-password" layout="row" layout-sm="column"
layout-align="space-between center">
<md-checkbox class="remember-me" ng-model="data.cb1" aria-label="Remember Me">
<span translate="LOGIN.REMEMBER_ME">Remember Me</span>
</md-checkbox>
<a ui-sref="app.pages_auth_forgot-password" class="forgot-password md-accent-color"
translate="LOGIN.FORGOT_PASSWORD">Forgot Password?</a>
</div>
<md-button class="md-raised md-accent" aria-label="LOG IN" translate="LOGIN.LOG_IN"
translate-attr-aria-label="LOGIN.LOG_IN"
ng-click="vm.submitPost(vm.form);">
LOG IN
</md-button>
</form>
I have a Node.js project with sessions and in my index.js I have the following:
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
app.use(session({
secret: config('session_secret'),
store: new MongoStore({ mongooseConnection: mongoose.connection }),
resave: true,
saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());
If you don't use MongoDB you can drop the "store" element. Then it uses the default store which is MemoryStore.
To prevent non-authenticated users accessing a page you can do this:
router.get('/secure-page', isLoggedIn, function(req, res) {
res.json({secure: "page"});
});
function isLoggedIn(req, res, next) {
// if user is authenticated in the session, carry on
if (req.isAuthenticated()) {
return next();
}
else {
// redirect to login page.
res.redirect('/login');
}
}
One way is storage values the session with express session, and then interceptade each route with middleware as verify if user is logged or not, somelike this...
Middleware autentic:
module.exports = function(req, res, next) {
if(!req.session.user) {
return res.redirect('/');
}
return next();
};
req.session.user is a variable create in session in login controller for storage username.
And intercept the route, verifying with user is logged:
...
app.get('pages/profile', autentic, controller.function);
...
If user is not logged will redirect to home page.
But, I suggest you to use passport.js:
Passport is authentication middleware for Node.js. Extremely flexible
and modular, Passport can be unobtrusively dropped in to any
Express-based web application. A comprehensive set of strategies
support authentication using a username and password, Facebook,
Twitter, and more.
See the docs for learn how to use and search here in the stack overflow too.
I'm trying to use redis with express to create a user login and session. I test the route using this curl script:
curl -d 'email=testEmail&password=testPass' http://localhost:3000/users/session
When I do this, passport works fine through serialization, and then it returns http 302. I haven't figured out what it does after serialization, but when I try it in the browser with my login html form instead of curl, It shows me "Unauthorized" 401, and I don't see any of my console logs. Here's my app.js:
var express = require('express')
, fs = require('fs')
, cons = require('consolidate')
, http = require('http')
, flash = require('connect-flash')
, passport = require('passport')
, RedisStore = require( "connect-redis" )(express) //for sessions (instead of MemoryStore)
, redis = require('redis')
, env = process.env.NODE_ENV || 'development'
, config = require('./config/config')[env]
, db = redis.createClient(config.db.port, config.db.host);
db.select(config.db.users)
db.auth(config.db.auth);
var app = express();
//require passport strategies (see code block below)
require('./config/passport')(passport, config, app)
app.use('/assets', express.static(__dirname + '/public'));
app.use('/', express.static(__dirname + '/'));
app.set('views', __dirname + '/views');
app.set('view engine', 'html');
app.configure(function(){
app.set('config', config);
app.set('db', db);
app.set('port', process.env.PORT || 3000);
app.engine('.html', cons.swig);
app.use(express.logger('dev'))
app.use(express.favicon(__dirname + '/public/img/favicon.ico'));
app.use(express.cookieParser())
app.use(express.bodyParser()) //enables req.body
app.use(express.methodOverride()) //enables app.put and app.delete (can also just use app.post)
app.use(express.session({
secret: 'topsecret',
cookie: {secure: true, maxAge:86400000},
store: new RedisStore({
client:db,
secret:config.db.auth
})
}));
app.use(flash())
app.use(passport.initialize())
app.use(passport.session())
app.use(app.router)
});
// Bootstrap routes
require('./config/routes')(app, passport);
http.createServer(app).listen(app.get('port'), function(){
console.log("Express server listening on port " + app.get('port')+', mode='+env);
});
And the session POST route:
app.post('/users/session', passport.authenticate('local', {successRedirect: '/', failureFlash: 'Invalid email or password.', successFlash: 'Welcome!'}), users.session);
I could only really find examples of passport with mongodb, so I'm not sure about the following. I attempt to find a user, but I'm not sure about the callbacks or what passport is doing with the user info when I return done:
passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password' },
function(email, password, done) {
var db = app.get('db')
var multi = db.multi();
db.get('email:'+email, function(err, uid){
if (err) { console.log(err); return err }
if (!uid) { console.log('no uid found'); return null }
console.log('found '+uid)
db.hgetall('uid:'+uid, function(err, user){
if (err) { console.log(err); return err }
if (!user) {
console.log('unkwn usr')
return done(null, false, { message: 'Unknown user' })
}
if (password != user.password) {
console.log('invalid pwd')
return done(null, false, { message: 'Invalid password' })
}
console.log('found user '+user) //I see this fine with curl, but no logs with browser
return done(null, user)
});
});
}
))
Passport serialization:
passport.serializeUser(function(user, done) {
console.log('passport serializing...'+user.name)
done(null, user.name) //no idea what happens to it after this. returns a 302 with curl, and 401 with browser
})
Why does this act differently with a browser than with curl? Any help or comments much appreciated!
Figured it out! To use passport with any database (not just mongo), I would recommend trying it with a dummy user first. Here's what I did.
Login Form (login.html):
<form method="post" action="http://localhost:3000/users/session" name="loginform">
<input id="login_input_username" type="text" name="email" />
<input id="login_input_password" type="password" name="password" autocomplete="off" />
<input type="submit" name="login" value="Submit" />
</form>
Routing (route.js):
app.post('/users/session', passport.authenticate('local'),
function(req, res){
res.end('success!');
});
Passport Local Strategy setup (passport.js):
passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password' },
function(email, password, done) {
//find user in database here
var user = {id: 1, email:'test', password:'pass'};
return done(null, user);
}
));
passport.serializeUser(function(user, done) {
//serialize by user id
done(null, user.id)
});
passport.deserializeUser(function(id, done) {
//find user in database again
var user = {id: 1, email:'test', password:'pass'};
done(null, user);
})
Although I'm wondering if it's necessary to find user in my database twice, or if I'm using deserialize incorrectly.