Passport.js strategy with no field required - node.js

I'm surprised that it's so hard to find, or maybe something wrong with me.
I need a passport.js strategy that requires no fields - to run authentication with it on simple user GET request to '/' and manipulate user session data to, for example, make a new 'guest' user.
Maybe i can do this via passport local-strategy? Or am i need to create a custom one?
I saw this Configure Passport to accept request without body? question, yet i just simply can't figure out how the suggested in answer wrapping will change anything.
Edit: here's what I'm trying to achieve
passport.use('guest', new LocalStrategy({
passReqToCallback: true
},
function (req, NOusername, NOpassword, done) {
////NO PASSWORD OR USERNAME checks here, just deciding what kind of temporary user needs to be created///
if (req.session.passport) {
///create new user with previous session data
done(null,user)
}
else {
///create clean user
done(null, user)
})
)
and then in routes.js
app.get('/', passport.authenticate('guest'), function (req, res) {
res.render('PeerRoom.ejs', req.user);
});

Edit: Another approach could be using req.logIn and skip dealing with the strategies altogether
app.get('/', yourCustomGuestAuthenticationMiddleware, function (req, res) {
res.render('PeerRoom.ejs', req.user);
});
function yourCustomGuestAuthenticationMiddleware(req, res, next){
// No need to do anything if a user already exists.
if(req.user) return next();
// Create a new user and login with that
var user = new User({name: 'guest'+Math.random().toString() });
user.save();
req.logIn(user, next);
}
To make a new guest user, simply do it instead of rejecting an authentication
Here's a modified example from the docs
var passport = require('passport'),
LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) return done(err);
if (!user) {
/* HERE, INSTEAD OF REJECTING THE AUTHENTICATION */
// return done(null, false, { message: 'Incorrect username.' });
/* SIMPLY CREATE A NEW USER */
var user = new User({ name: 'guest'+Math.random().toString() });
user.save();
/* AND AUTHENTICATE WITH THAT */
return done(null, user);
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));

Related

How to handle, check if user is logged in with Passport Slack Strategy and Passport-local

Hello I am needing some assistance with setting up a function to check if a user is logged in or not with Passport.js using the slack strategy.
I have a localstrategy which works fine but I also want to implement a sign in with slack as well.
I have added my app to slack and set the redirect URL. Everything is working fine however regardless if a user signs in or not they can still access the main page from the URL.
Example I have my login page 123.456.7.891:3000/login and when a user logs in through the local login or slack it redirects to 123.456.891:3000/index. Well if you know the URL for index you can just navigate to it.
For the local strategy I used this function to prevent that. It checks if the user is logged in or not.
function isLoggedIn(req, res, next){
if(req.isAuthenticated()){
return next();
}
req.flash("error", "Must be signed in first to access this page.");
res.redirect("/login");
}
And than I simply add the isLoggedIn function to the route like this.
app.get("/QAApplicationHub", isLoggedIn, (req, res) => {
Application.find({}, (err, allApplications) => {
if(err){
console.log(err);
} else {
res.render("index", { application: allApplications });
// username: req.user.name
}
});
});
The issue I am having with when a user logs in with slack is when they are redirected to the redirect URL it just takes them back to the login page stating that the user must be logged in to access the page. The message appears because I have flash set up to show the user the error. It seems that with my current code the isLoggedIn only checks for the local login and not slack.
So how can I implement the isLoggedIn function for both the local and slack strategy? Or what method is it that I need to implement for it to work for both.
This is my code for Passport-slack.
// Configure the Slack Strategy
passport.use(new SlackStrategy({
clientID: process.env.SLACK_CLIENT_ID = '123456',
clientSecret: process.env.SLACK_CLIENT_SECRET ='123abc',
}, (accessToken, scopes, team, extra, profiles, done) => {
done(null, profiles.user);
}));
//=============================
//LOCAL LOGIN MIDDLEWARE
//=============================
app.post("/login", passport.authenticate("local", {
successRedirect: "/QAApplicationHub",
failureRedirect: "/login",
failureFlash: true
}));
app.get("/logout", (req, res) => {
req.logout();
req.flash("success", "Successfuly signed out!")
res.redirect("/login");
});
// =============================
// PASSPORT-SLACK MIDDLEWARE
// =============================
// path to start the OAuth flow
app.post('/auth/slack', passport.authorize('slack', {
successRedirect: "/QAApplicationHub",
failureRedirect: "/login",
failureFlash: true
}));
// OAuth callback url
app.get('/auth/slack/callback',
passport.authorize('slack',
(req, res) => {
res.redirect('/QAApplicationHub')
}));
I believe users are stored in the req.user property. Try this:
function isLoggedIn(req, res, next){
if(!req.user){
req.flash("error", "Must be signed in first to access this page.");
res.redirect("/login");
} else {
return next();
}
}
So turns out I was missing quit a bit of code to make this complete. First I needed to add the user schema for slack. Second needed to add a function to create the user and save it to my db and a function to check if a user had already signed in with slack and to just compare the ID instead of making a new instance of the user to the DB. Also needed to update the serialize user and de-serialize user.
Here's my schema
let mongoose = require("mongoose"),
passportLocalMongoose = require("passport-local-mongoose");
let UserSchema = new mongoose.Schema({
local: {
name: String,
username: String,
password: String
},
slack: {
username: String,
slackid: Number,
name: String
}
});
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", UserSchema);
And than the set up for the passport strategies.
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id).then((user) => {
done(null, user.id);
});
});
passport.use(new LocalStrategy(User.authenticate()));
// Configure the Slack Strategy
passport.use(
new SlackStrategy({
callbackURL: "/auth/slack/redirect",
clientID: keys.slack.clientID,
clientSecret: keys.slack.clientSecret
}, (accessToken, refreshToken, profile, done) => {
console.log(profile);
// Check if user already exist in DB
User.findOne({username: profile.displayName}).then((currentUser) => {
if(currentUser){
// already have the user
console.log('User is', currentUser);
done(null, currentUser);
} else {
// If not, create new user in DB
new User({
username: profile.displayName,
slackid: profile.id
}).save().then((newUser) => {
console.log("new user created: " + newUser);
done(null, newUser);
});
}
});
}));
Also needed to update the routes for the auth and call back.
app.post('/auth/slack',
passport.authenticate('Slack', {
scope:['identity.basic'],
}));
// OAuth callback url
app.get('/auth/slack/redirect', passport.authenticate('Slack'), (req, res) => {
res.redirect("/QAApplicationHub");
});
Hopes this helps others in need of passport.js help. For details info I recommend looking up The Net Ninja on YT. He helped me a lot with my code gap and helped understand what was actually going on.

Check if user exists in passport-local

I have an Express app with the passport-local strategy, using Mongoose for storing user Accounts. I had a freelancer write this part of the app for me because I couldn't make sense of how to build the login system from beginning to end (every single tutorial I found did it in a different way). The drawback of this is that I don't understand what each part does. This is in my app.js file:
const Account = require('./models/db/AccountsSchema');
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(Account.authenticate()));
passport.serializeUser(Account.serializeUser());
passport.deserializeUser(Account.deserializeUser());
and this is in routes/index.js:
router.post('/register', function(req, res) {
Account.register(new Account({
username: req.body.username,
name: req.body.name
}), req.body.password, function(err, account) {
if (err) {
console.log(err);
} else {
console.log(account);
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login'
})(req, res, function(err, user) {
res.redirect('/');
});
}
});
});
along with:
router.post('/login',
passport.authenticate('local'),
function(req, res) {
res.redirect('/');
}
);
Now in the login POST request I want to have a check for whether a user with that particular username exists, so that if the password is wrong at least I can tell the user that the password is wrong. User enumeration is not a security concern here.
Where in my code can I incorporate a database check for whether an Account with the specified username exists?
In Account.authenticate() function. As it sets the LocalStrategy in your case.
setting your local strategy:
passport.use(new LocalStrategy(Account.authenticate()));
Sample Code:
function authenticate(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);
});
}

PassportJS not redirecting upon save

I am trying to redirect a user once he signs up or if the username is already present in the db, the function works well but I can not redirect and I am totally confused on what to do next.
Here is my passport file
var LocalStrategy = require('passport-local').Strategy;
var User = require('./models/user');
module.exports = function(passport) {
passport.serializeUser(function(user, done) {
done(null, user);
});
// used to deserialize the user
passport.deserializeUser(function(user, done) {
done(null, user);
});
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'email' : email }, function(err, user) {
// if there are any errors, return the error
if (err) {
return done(err);
}
if(user) {
if(user.validPassword(password)) {
return done(null, user);
} else {
return done(null, true, req.flash('signupMessage', 'That email is already in DB.'));
}
} else {
var newUser = new User();
newUser.email = email;
newUser.password = newUser.generateHash(password);
newUser.save(function(err) {
if (err)
return done(err);
return done(null, newUser);
});
}
});
});
}));
}
and my route
app.post('/signup',passport.authenticate('local-signup', {
successRedirect : '/timeslot', // redirect to the secure profile section
failureRedirect : '/', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
I have also tried doing
app.post('/signup',function(req, res, next) {
passport.authenticate('local-signup', function(err, user, info) {
if (user) {
res.redirect('/timeslot');
}
else res.redirect('/');
})(req, res, next);
});
but it is not working at all. I am desperate to fix this but dont know how.
I met the same problems with passport redirects. So my code began work just as
app.get("/auth/facebook/callback", passport.authenticate("facebook", { failureRedirect: config.loginPage }), function(req,res) {
res.redirect(config.redirectAfterLogin);
});

passport-local with node-jwt-simple

How can I combine passport-local to return a JWT token on successful authentication?
I want to use node-jwt-simple and looking at passport.js I am not sure how to go about.
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
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);
});
}
));
Is it possible to return the token when calling done() ?
Something like this... (just pseudo code)
if(User.validCredentials(username, password)) {
var token = jwt.encode({username: username}, tokenSecret);
done(null, {token : token}); //is this possible?
}
If not, how can I return the token?
I figured it out!
First of all you need to implement the correct strategy. In my case LocalStrategy, and you need to provide your validation logic. For example sake let's use the one in passport-local.
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
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);
});
}
));
the verify call back you provide function(username, password, done) will take care of finding your user and checking if the password matches (beyond the scope of the question and my answer)
passport.js expects several pieces for it to work, one is that you return the user in the strategy. I was trying to change that part of the code, and that was wrong. The callback expects false if the validation fails and an object (the validated user) if you are successful.
Now.... how to integrate JWT?
In your login route you will have to handle a successful auth or an unsuccessful one. And it is here that you need to add the JWT token creation. Like so:
(remember to disable the session, otherwise you will have to implement the serialize and deserialize functions. And you don't need those if you are not persisting the session, which you are not if you are using a token based auth)
From passport-local examples: (with the JWT token added)
// POST /login
// This is an alternative implementation that uses a custom callback to
// achieve the same functionality.
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err) }
if (!user) {
return res.json(401, { error: 'message' });
}
//user has authenticated correctly thus we create a JWT token
var token = jwt.encode({ username: 'somedata'}, tokenSecret);
res.json({ token : token });
})(req, res, next);
});
And that is it! Now when you call /login and POST username and password (which should always be over SSL) the first code snippet above will try to find a user based on the username you provided and then check that the password matches (Of course you will need to change that to suit your needs).
After that your login route will be called and there you can take care of returning an error or a valid token.
Hope this will help someone. And if I have made any mistakes or forgot something let me know.
This is a great solution, I just want to add this:
var expressJwt = require('express-jwt');
app.use('/api', expressJwt({secret: secret}));
I like to use "express-jwt" to validate the token.
btw: this article is great to learn how to handle the token in the client side, using Angular, in order to send it back with every request
https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/
Here's a boiler-plate I'm working on for specifically using api tokens only (no sessions...not that session are bad of course; just we're using token approach):
https://github.com/roblevintennis/passport-api-tokens

Verify access/group in Passport.js

I would like to use passport.js to verify that when users hit certain endpoints that they not only have the correct password but are a member of a specific group or have a certain access.
For simplicity sake if I have access levels of USER and ADMIN.
I can use passport to authenticate a password:
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);
});
}
));
Then with a route I can make sure the user passes auth:
app.get('/api/users',
passport.authenticate('local'),
function(req, res) {
res.json({ ... });
});
But lets say you need to have ADMIN acess to hit /api/users'. Do I need to write my own Strategies? IE do I need to have a local-user, and local-admin strategy, and in each verify the proper access levels?
I think I can do this pretty easily, but the problem arises when I need my site to have different auth methods (maybe sometimes use oauth), I would need to write custom *-user, *-admin strategies for each. Seems like overkill.
Other option is to just verify access/group in each route after the user has been authenticated. But I would prefer to do this in the middle-ware if possible.
Thanks
You could create a simple middleware that checks the group:
var needsGroup = function(group) {
return function(req, res, next) {
if (req.user && req.user.group === group)
next();
else
res.send(401, 'Unauthorized');
};
};
app.get('/api/users',
passport.authenticate('local'),
needsGroup('admin'),
function(req, res) {
...
});
This assumes that the object stored in req.user has a property group. This object is the one passed along from the strategy implementation and deserializeUser.
An alternative could be connect-roles, but I don't know how well that integrates with Passport.
EDIT: you could also combine Passport and the group-checking middleware:
var needsGroup = function(group) {
return [
passport.authenticate('local'),
function(req, res, next) {
if (req.user && req.user.group === group)
next();
else
res.send(401, 'Unauthorized');
}
];
};
app.get('/api/users', needsGroup('admin'), function(req, res) {
});

Resources