Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
API Auth with KeystoneJS

To implement API authentication in KeystoneJS, you need the following:

For key based authentication

  • Middleware that validates the key in the request body or a header

For session based authentication

  • An endpoint that handles signin
  • An endpoint that handles signout
  • Middleware that validates the session

Examples of both methods are below.

Note that the code in each file below would normally be spread across several files, depending on your project organisation, e.g. route handlers in a /routes/api/... folder, and route bindings in /routes/index.js.

// check that the key has been provided in the request body,
// could also be a header
function checkAPIKey(req, res, next) {
// you would have the key in an env variable or load it from
// your database or something.
if (req.body.apiKey === SECRET_API_KEY) return next();
return res.status(403).json({ 'error': 'no access' });
}
// then bind that middleware in your routes before any paths
// that should be protected
app.all('/api*', checkAPIKey);
// the rest of your api endpoints go below here, e.g.
app.get('/api/stuff', getStuff);
// create a route that handles signin
function signin(req, res) {
if (!req.body.username || !req.body.password) return res.json({ success: false });
keystone.list('User').model.findOne({ email: req.body.username }).exec(function(err, user) {
if (err || !user) {
return res.json({
success: false,
session: false,
message: (err && err.message ? err.message : false) || 'Sorry, there was an issue signing you in, please try again.'
});
}
keystone.session.signin({ email: user.email, password: req.body.password }, req, res, function(user) {
return res.json({
success: true,
session: true,
date: new Date().getTime(),
userId: user.id
});
}, function(err) {
return res.json({
success: true,
session: false,
message: (err && err.message ? err.message : false) || 'Sorry, there was an issue signing you in, please try again.'
});
});
});
}
// you'll want one for signout too
function signout(req, res) {
keystone.session.signout(req, res, function() {
res.json({ 'signedout': true });
});
// also create some middleware that checks the current user
// as long as you're using Keystone's session management, the user
// will already be loaded if there is a valid current session
function checkAuth(req, res, next) {
// you could check user permissions here too
if (req.user) return next();
return res.status(403).json({ 'error': 'no access' });
}
// add an API endpoint for signing in _before_ your protected routes
app.post('/api/signin', signin);
app.post('/api/signout', signout);
// then bind that middleware in your routes before any paths
// that should be protected
app.all('/api*', checkAuth);
// the rest of your api endpoints go below here, e.g.
app.get('/api/stuff', getStuff);
@jorisw

This comment has been minimized.

Copy link

commented May 27, 2015

There's a closing curly brace missing for the signout method.

@bmulyawan

This comment has been minimized.

Copy link

commented Sep 1, 2015

in term of security.. for session based isn't it better with token key as userId never get changed? and

 return res.json({
        success: true,
        session: false,
        message: (err && err.message ? err.message : false) || 'Sorry, there was an issue signing you in, please try again.'
      });

just wondering why not success:false ?

@MikelArnaiz

This comment has been minimized.

Copy link

commented Jul 8, 2016

With a correct username and wrong password you get a 200 code. I changed the keystone.session.signin error callback to:

return res.status(401).json({ 'error': 'wrong credentials' });

@bradwbradw

This comment has been minimized.

Copy link

commented Aug 11, 2016

You can use a hack-ish method to determine if a user is logged in from the same origin, that doesn't require additional code.

This doesn't help with signing in or registering, but it is can determine if someone is logged in already.

Just send a GET request to the built-in keystone api: domain.com/keystone/api/listname

you can use any list for "listname". If it comes back with actual json data, then you're logged in. If it comes back with the html of the sign in page, then you aren't logged in!

@intrnet

This comment has been minimized.

Copy link

commented Jul 16, 2018

Hi,

Below is my JWT authentication sample which I would like to discuss with you. It works well but maybe there is a way to do that better. I will show you how it works and let me know what you think about it.

routes/views/session/signinjwt.js

var keystone = require('keystone'),
    async = require('async'),
    jwt = require('jsonwebtoken'),
    _ = require('lodash'),
    crypto = require('crypto'),
    utils = require('keystone-utils');
 
exports = module.exports = function(req, res) {
    
    if (req.user && req.cookies.uToken) {
        return res.redirect(req.cookies.target || '/me');
    }
     
    var view = new keystone.View(req, res),
        locals = res.locals;
        locals.section = 'session';
        locals.form = req.body;

    view.on('post', { action: 'signinjwt' }, function(next) {
        
        if (!req.body.email || !req.body.password) {
            req.flash('error', 'Please enter your username and password.');
            return next();
        }
        
            var token = '',
                userdata = {};
                async.series([   
                    function (next) {             
                        var User = keystone.list(keystone.get('user model'));
                        if (typeof req.body.email === 'string' && typeof req.body.password === 'string') {
                            
                            if (!utils.isEmail(req.body.email)) {
                                return next({message:'Incorrect email or password'});
                            }
                            var emailRegExp = new RegExp('^' + utils.escapeRegExp(req.body.email) + '$', 'i');
                         
                            User.model.findOne({ email: emailRegExp }).exec(function (err, user) {
                                if (user) {
                                    user._.password.compare(req.body.password, function (err, isMatch) {
                                        if (!err && isMatch) {
                                            userdata = user;
                                            return next();
                                        } else {
                                            return next({ message: err || 'Incorrect email or password' });
                                        }
                                    });
                                } else {
                                    return next({ message: err });
                                }
                            });
                        } else {
                            return next({ message: 'Incorrect user or password'});
                        }
                    },
                    function(next) { 
                        token = jwt.sign({id: userdata._id, email:userdata.email}, keystone.get('jwt secret'), { expiresIn: '15s' });
                        keystone.app.locals.user = userdata;
                        
                        return next();
                    },
                    function(next) {
                        res.cookie('uToken', token,  { maxAge: 900000, httpOnly: true });
                        res.clearCookie('afterLoginUrl');   
                        return next();                     
                    },
                    function(next) {
                        if (req.body.target && !/join|signinjwt/.test(req.body.target)) {
                            return res.redirect(req.body.target); 
                            return next();        
                        } else {
                            var afterLoginUrl = req.cookies.afterLoginUrl || '/me'; 
                            
                            return res.redirect(afterLoginUrl);
                          
                            return next(); 
                        }
                    }       
                ], function(err) {
                    if (err) {
                        req.flash('error', err.message || 'Sorry, there was an issue signing you in, please try again.');
                        return res.redirect('/signinjwt');
                    }
                });
    });
    
    view.render('session/signinjwt');
    
}

routes/views/session/signoutjwt.js

var keystone = require('keystone');

exports = module.exports = function(req, res) {
    
    var view = new keystone.View(req, res),
        locals = res.locals;
    
    locals.section = 'session';
    res.clearCookie('uToken');
    keystone.app.locals.user = null;
    
    res.redirect('/');
};

routes/middleware.js

   ...

exports.initLocals = function (req, res, next) {
   ...

   locals.user = keystone.app.locals.user;

   ...

}

   ...

exports.jwtCheck = function (req, res, next) {
 
    if (!req.cookies.uToken) {
        req.user = null;
        res.cookie('afterLoginUrl', req.originalUrl, { maxAge: 900000, httpOnly: true });
        req.flash('error', 'Please sign in to access this page.');
        res.redirect('/signinjwt');
    } else {
        
        jwt.verify(req.cookies.uToken, keystone.get('jwt secret'), function(err, decoded) {
            if(err) {
                req.user = null;
                res.clearCookie('uToken');
                res.cookie('afterLoginUrl', req.originalUrl, { maxAge: 900000, httpOnly: true });
                req.flash('error', 'Please sign in to access this page.');
                res.redirect('/signinjwt'); 
            } else {
                 
                next();
            }
            
        });
    }
};
   ...

/routes/index.js


  ...

    app.all('/signinjwt', routes.views.session.signinjwt);
    app.get('/signoutjwt', routes.views.session.signoutjwt);
    app.all('/needauth*', middleware.jwtCheck);

  ...

/keystone.js

   ...

keystone.init({
...
'session': false,
...
});

   ...

@agmt5989

This comment has been minimized.

Copy link

commented Jan 13, 2019

I'm still wondering at #4880, how users fare who have more than one email saved. So far, they would need to supply both at login. Is there a way to make emails work as a relationship field, and still able to participate in authentication?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.