using koa and passport for authenication - authentication

I'm using koa and passport trying to implement middleware to prevent access to URIs when not authenticated.
var koa = require('koa');
var session = require('koa-generic-session');
var bodyParser = require('koa-bodyparser');
var koaRouter = require('koa-router');
var passport = require('koa-passport');
var views = require('co-views');
var render = views('.', { map: { html: 'swig' }});
var localStrategy = require('passport-local').Strategy;
var app = koa();
var router = koaRouter();
app.keys = ['secret'];
app.use(session());
app.use(bodyParser());
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
passport.use(new localStrategy(function(username, password, done) {
if (username === 'user1' && password === 'password2') {
done(null, { userId: 99, userName: 'redBallons' });
} else {
done(null, false);
}
}));
router.get('/login', function *(next) {
this.body = yield render('index.html');
});
router.post('/login', passport.authenticate('local', {
successRedirect: '/secretBankAccount',
failureRedirect: '/login'
}));
router.get('*', function *(next) {
if (! this.isAuthenticated()) {
console.log('not authenticated');
this.redirect('/login');
} else {
console.log('authenticated');
yield next;
}
});
router.get('/secretBankAccount', function *(next) {
this.body = '2 dollars';
});
app.use(router.routes());
app.listen(8080);
however, I can never get to my secretBankAccount. I can enter the correct user and password and can see the authenicated message, but the yield next in router.get('*') does not pass me through to the next routing function

When using koa-router it is expected that only one route is hit. So when you hit the '*' route it won't hit another route even if you yield next.
So you should replace the universal route with your own authentication middleware:
app.use(function*(next) {
if (this.isAuthenticated()) {
yield next
} else {
this.redirect('/login')
}
});
The authentication middleware will force you to do your routing with two routing objects instead of one. This is so you can distinguish between public and secured routes. So something like:
var public = new koaRouter();
public.get('/login', function *(next) {
this.body = yield render('index.html');
});
public.post('/login', passport.authenticate('local', {
successRedirect: '/secretBankAccount',
failureRedirect: '/login'
}));
app.use(public.routes());
app.use(function*(next) {
if (this.isAuthenticated()) {
yield next;
} else {
this.redirect('/login');
}
})
var secured = new koaRouter();
secured.get('/secretBankAccount', function *(next) {
this.body = '2 dollars';
});
app.use(secured.routes());
In the above example a request will first hit the public routing middleware. Then if it doesn't match the current request with a public route it will move onto the authentication middleware. If isAuthenticated() is false a redirect will occur. If isAuthenticated() is true it'll move onto the secured routing.
This approach is based on the koa-passport-example project which was created by the author of koa-passport.

Answer from peadar-doyle is the way to do this but needs updating to avoid the warning: koa deprecated Support for generators will be removed in v3.
Here's the updated version. I'm sending 401 instead of redirecting:
// all requests must now be authenticated
app.use(async (ctx, next) => {
if (ctx.isAuthenticated()) {
await next();
} else {
ctx.body = "access denied";
ctx.status = 401;
}
})

Related

Sequelize model property undefined Express.js controller after auth with passport-jwt

I am using passport-jwt to verify access to a given route in express.js, and then return a Sequelize model to the final controller. The code looks like:
The auth strategy:
const passportStrategy = passport => {
const options = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.auth.ACCESS_TOKEN_SECRET
};
passport.use(
new Strategy(options, async (payload, done) => {
try {
const user = await User.findOne({ where: { email: payload.email }});
if (user) {
return done(null, {
user
});
}
return done(null, false);
}
catch (error) {
return done(error, false)
}
})
);
};
The route with the auth middleware
router.get('/:user_id/psy', passport.authenticate('jwt', { session: false }), patientsController.getPatientPsy);
The controller function
const getPatientPsy = async (req, res) => {
const authenticatedUser = req.user;
if (authenticatedUser.userType !== "patient") {
res.status(500).send("Big time error");
}
}
If I console.log(authenticatedUser) in the getPatientPsy() controller it successfully prints the Sequelize model with it's dataValues and so on, but when I try to access any property, be it userType or any other it consistently returns undefined.
In the passport-jwt authentication once a User has been found that matches the extracted JWT token, afaik it is returned synchronously and made it available in the req.user object, and I can print it with console.log, but why can't I access the model's properties?
I've tried to make the getPatientPsy() controller a sync function but it doesn't work either.
Thank you.
All right this is embarrassing, by default Passport.js returns the done(null, user) in the req.user property, and since I am returning { user }, I had to access through req.user.user.

stateless session api request

I am building a simple app that uses JWT for authentication. But I keeps on getting the error saying the route I GET to require a call back function.
What do I expect?
I should be getting the current user's data back.
What do I actually get?
Error: Route.get() requires a callback function but got a [object Object]
Route:
const authenticate = require("../middlewares/authenticate");
const usersController = require("../controllers").users;
app.get("/users/me", authenticate, usersController.getMe);
Model:
"use strict";
const jwt = require("jsonwebtoken");
module.exports = (sequelize, DataTypes) => {
var User = sequelize.define(
"User",
{
email: DataTypes.STRING,
password: DataTypes.STRING
},
{
classMethods: {
associate: function(models) {
// associations can be defined here
},
findByToken: function(token) {
const User = this;
let decoded;
try {
decoded = jwt.verify(token, "leogoesger");
} catch (e) {
console.log(e);
}
return User.find({ where: { email: decoded.email } });
}
}
}
);
return User;
};
Middleware:
const { User } = require("../models/user");
const authenticate = (req, res, next) => {
console.log("called here");
const token = req.header("x-auth");
User.findByToken(token)
.then(user => {
if (!user) {
}
req.user = user;
req.token = token;
next();
})
.catch(e => {
res.status(401).send(e);
});
};
module.exports = { authenticate };
Controller:
module.exports = {
getMe(req, res) {
res.status(200).send({ message: "hello" });
}
};
Your authenticate module exports an object, yet you do this:
const authenticate = require("../middlewares/authenticate");
which means your const authenticate is an object, not your function. Change that to this:
const authenticate = require("../middlewares/authenticate").authenticate;
Or, change the module to export the function directly instead of exporting an object with the function in it.

How to explicitly pass user data to passport.authenticate

I'm making a webapp that uses Socket.io to pass information between the server and the client, one example being login information. The documentation for passport.authenticate says to use it like so:
app.post('/login', passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login' }));
However, my webapp is using Polymer client-side routing, so the only route my index.js has is this:
app.get('*', function (req, res) {
res.sendFile('./public/index.html', {root: '.'});
});
Instead, I'd like to do something like this:
io.on('connection', function(socket){
socket.on('login', function(data){
passport.authenticate('local', data);
});
});
However, this doesn't work as the authenticate function doesn't even get called right now. Is there a way to make passport work in such a scenario?
You can try something like below .
In your routes define and require the socket module, so you have access to use it in routes.
var express = require('express');
var app = express();
var server = http.createServer(app);
var io = require('socket.io').listen(server);
var router = express.Router();
var passport = require('passport');
io.on('connection', function(socket){
socket.on('login', function(data){
// call the routes
router.post('/login', function(request, response, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
// return next(err);
socket.emit('loginResult', { success: false });
}
if (!user) {
var message = "Invalid credentials";
socket.emit('loginResult', { success: false , message: message});
}
request.logIn(user, function (err) {
if (err) {
socket.emit('loginResult', { success: false });
}
// if want to save user in session
request.session.user = user;
// after success code
socket.emit('loginResult', { success: true , user : user});
});
})(request, response, next);
});
});
});
Hope this helps.
You can define your custom callback with passport.authenticate(). I have given a example below, you might wanna try that. Go here for more info.
io.on('connection', function(socket){
socket.on('login', function(data){
var req = {}
req.body = data
passport.authenticate('local', function(err, user, info) {
if (err) {
socket.emit('login', { success: false });
}
if (!user) {
socket.emit('login', { success: false });
}
// Set session
req.logIn(user, function(err) {
if (err) {
socket.emit('login', { success: false });
}
socket.emit('login', { success: true });
});
});
});
Update: Problem with previous code was, when using custom callbacks in passport authenticate it uses req object from the closure, which in this case was undefined as it was not in the router. I think, now that you can provide enough authentication data through req.body it should work.

Getting "error": "Unknown authentication strategy \"jwt\""

I'm implementing an authorization feature using Express, Mongoose, Passport and JWT.
I'm able to register a user ok. I'm able to authenticate and generate a JWT, which I can parse on the JWT site, but for some reason, I'm getting an Unknown authentication strategy error message.
I have all my code blocks laid out on a Plunker instance at:
https://plnkr.co/edit/ZNjQwcZ4rMymzBXNy5nX?p=catalogue
Here is my passport.js file, which contains my strategy:
var JwtStrategy = require('passport-jwt').Strategy;
// load up the user model
var User = require('../models/user');
var config = require('../config/database'); // get db config file
module.exports = function(passport) {
var opts = {};
opts.secretOrKey = config.secret;
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
User.findOne({id: jwt_payload.id}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
done(null, user);
} else {
done(null, false);
}
});
}));
};
Here is what my authentication.js file looks like:
var express = require('express');
var router = express.Router();
var jwt = require('jwt-simple');
var config = require('../config/database');
var User = require('../models/user');
router.route('/')
.post(function(req, res) {
User.findOne({
name: req.body.name
}, function(err, user) {
if (err)
res.send(err);
if (!user) {
res.send({success: false, msg: 'Authentication failed. User not found.'});
} else {
// check if password matches
user.comparePassword(req.body.password, function (err, isMatch) {
if (isMatch && !err) {
// if user is found and password is right create a token
var token = jwt.encode(user, config.secret);
// return the information including token as JSON
res.json({success: true, token: 'JWT ' + token});
} else {
res.send({success: false, msg: 'Authentication failed. Wrong password.'});
}
});
}
});
});
module.exports = router;
Here is the endpoint I'm calling that is generating the error:
var express = require('express');
var router = express.Router();
var jwt = require('jwt-simple');
var config = require('../config/database');
var passport = require('passport');
var User = require('../models/user');
router.route('/')
.get(passport.authenticate('jwt', { session: false}), function(req, res) {
var token = getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function(err, user) {
if (err) throw err;
if (!user) {
return res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
} else {
res.json({success: true, msg: 'Welcome in the member area ' + user.name + '!'});
}
});
} else {
return res.status(403).send({success: false, msg: 'No token provided.'});
}
});
getToken = function (headers) {
if (headers && headers.authorization) {
var parted = headers.authorization.split(' ');
if (parted.length === 2) {
return parted[1];
} else {
return null;
}
} else {
return null;
}
};
module.exports = router;
You forgot to include your own passport.js module in the application. This leads nodejs to not find the definition of JWTStrategy which is ultimately causing the error that you see.
In your endpoint file, just include the local passport.js file:
var express = require('express');
var router = express.Router();
var jwt = require('jwt-simple');
var config = require('../config/database');
var passport = require('passport');
require('./passport')(passport) // as strategy in ./passport.js needs passport object
var User = require('../models/user');
router.route('/')
.get(passport.authenticate('jwt', { session: false}), function(req, res) {
var token = getToken(req.headers);
...
if you look at your passport configuration file (passport.js) you will see
module.exports = function (passport) {
//bla bla bla
}
as you see it needs passport instance
now how to pass this instance to your passport.js file
simply
var passport = require('passport');// create a passport instance
var myPassportService = require('../config/passport')(passport);// pass it into passport.js file
hope this helps you
Just on the off chance that someone is having the same issue but it isn't resolved by the top answer.
My issue ended up being accidently moving node modules into another directory and then of course running npm install to fix module import errors. I ended up with two node_modules directory and although the server started fine, passport errored when it was called.
I eventually found the blunder and removed the second node_modules directory and the Unknown authentication strategy “jwt” was no resolved.

Implement Remember Me functionality in Express + PassportJS + Redis app

I'm trying to build authentication system with ExpressJS and PassportJS. For session store I use Redis. I wanna use Remember Me. Every time when the user signs in and has marked "remember me" check-box, it should automatically sign in by next visit on site. I have downloaded an example app form Github https://github.com/jaredhanson/passport-remember-me and change for my using.
var express = require('express')
, passport = require('passport')
, LocalStrategy = require('passport-local').Strategy
, mongodb = require('mongodb')
, mongoose = require('mongoose')
, bcrypt = require('bcrypt')
, SALT_WORK_FACTOR = 10
, RedisStore = require('connect-redis')(express);
mongoose.connect('localhost', 'test');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function callback() {
console.log('Connected to DB');
});
// User Schema
var userSchema = mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true},
accessToken: { type: String } // Used for Remember Me
});
// Bcrypt middleware
userSchema.pre('save', function(next) {
var user = this;
if(!user.isModified('password')) return next();
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if(err) return next(err);
bcrypt.hash(user.password, salt, function(err, hash) {
if(err) return next(err);
user.password = hash;
next();
});
});
});
// Password verification
userSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if(err) return cb(err);
cb(null, isMatch);
});
};
// Remember Me implementation helper method
userSchema.methods.generateRandomToken = function () {
var user = this,
chars = "_!abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
token = new Date().getTime() + '_';
for ( var x = 0; x < 16; x++ ) {
var i = Math.floor( Math.random() * 62 );
token += chars.charAt( i );
}
return token;
};
// Seed a user
var User = mongoose.model('User', userSchema);
var usr = new User({ username: 'bob', email: 'bob#example.com', password: 'secret' });
usr.save(function(err) {
if(err) {
console.log(err);
} else {
console.log('user: ' + usr.username + " saved.");
}
});
// 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.
//
// Both serializer and deserializer edited for Remember Me functionality
passport.serializeUser(function(user, done) {
var createAccessToken = function () {
var token = user.generateRandomToken();
User.findOne( { accessToken: token }, function (err, existingUser) {
if (err) { return done( err ); }
if (existingUser) {
createAccessToken(); // Run the function again - the token has to be unique!
} else {
user.set('accessToken', token);
user.save( function (err) {
if (err) return done(err);
return done(null, user.get('accessToken'));
})
}
});
};
if ( user._id ) {
createAccessToken();
}
});
passport.deserializeUser(function(token, done) {
User.findOne( {accessToken: token } , function (err, user) {
done(err, user);
});
});
// Use the LocalStrategy within Passport.
// Strategies in passport require a `verify` function, which accept
// credentials (in this case, a username and password), and invoke a callback
// with a user object. In the real world, this would query a database;
// however, in this example we are using a baked-in set of users.
passport.use(new LocalStrategy(function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
user.comparePassword(password, function(err, isMatch) {
if (err) return done(err);
if(isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Invalid password' });
}
});
});
}));
var app = express();
// configure Express
app.configure(function() {
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.engine('ejs', require('ejs-locals'));
app.use(express.logger());
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.session({
store: new RedisStore({ host: '127.0.0.1', port: 6379, prefix: 'chs-sess' }),
secret: '4Md97L1bL4r42SPn7076j1FwZvAiqube',
maxAge: new Date(Date.now() + 3600000)
}));
// Remember Me middleware
app.use( function (req, res, next) {
if ( req.method == 'POST' && req.url == '/login' ) {
if ( req.body.rememberme ) {
req.session.cookie.maxAge = 2592000000; // 30*24*60*60*1000 Rememeber 'me' for 30 days
} else {
req.session.cookie.expires = false;
}
}
next();
});
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.use(express.static(__dirname + '/../../public'));
});
app.get('/', function(req, res){
res.render('index', { user: req.user });
});
app.get('/account', ensureAuthenticated, function(req, res){
res.render('account', { user: req.user });
});
app.get('/login', function(req, res){
res.render('login', { user: req.user, message: req.session.messages });
});
// POST /login
// Use passport.authenticate() as route middleware to authenticate the
// request. If authentication fails, the user will be redirected back to the
// login page. Otherwise, the primary route function function will be called,
// which, in this example, will redirect the user to the home page.
//
// curl -v -d "username=bob&password=secret" http://127.0.0.1:3000/login
//
/***** This version has a problem with flash messages
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login', failureFlash: true }),
function(req, res) {
res.redirect('/');
});
*/
// POST /login
// This is an alternative implementation that uses a custom callback to
// acheive the same functionality.
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err) }
if (!user) {
req.session.messages = [info.message];
return res.redirect('/login')
}
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/');
});
})(req, res, next);
});
app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
app.listen(3000, function() {
console.log('Express server listening on port 3000');
});
// Simple route middleware to ensure user is authenticated.
// Use this route middleware on any resource that needs to be protected. If
// the request is authenticated (typically via a persistent login session),
// the request will proceed. Otherwise, the user will be redirected to the
// login page.
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/login')
}
My app doesn't work with Remember Me, every time when I close the browser, I have to sign-in again. I don't know, what I have done wrong.
My second question is, how doe Remember Me works as usual? I have some idea but not exactly sure.