How can I protect an API endpoint with PassportJS?
I don't use passportjs yet but i've just done the same thing you're looking to do. Here's my example configuration:
// Example configuration
var express = require('express');
var routes = require('./routes');
var app = express();
app.configure(function(){
app.use(express.bodyParser());
app.use(express.cookieParser('shhhh, very secret'));
app.use(express.session());
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.compress());
app.use('/', express.static(expressAppdir)); // look for overrides on express server 1st
app.use('/', express.static(appDir));
// app.use(express.methodOverride());
app.use(app.router);
app.use(function(req, res, next){
var err = req.session.error
, msg = req.session.success;
delete req.session.error;
delete req.session.success;
res.locals.message = '';
if (err) res.locals.message = '<p class="msg error">' + err + '</p>';
if (msg) res.locals.message = '<p class="msg success">' + msg + '</p>';
next();
});
});
app.configure(function() {
// gets
app.get('/', routes.root);
app.get('/login', routes.login);
app.get('/logout', routes.logout);
app.get('/restricted/test/:slug', restrict, routes.restrictedGet); // must be last API route, slug is any request on the end of the routes that is requested.
app.post('/login', routes.loginPost);
});
function restrict(req, res, next) {
console.dir('restrict called');
if (req.session.user) {
next();
} else {
req.session.error = 'Access denied!';
res.redirect('/login');
}
}
//Routes.js file
// my dummy login (in a separate file)
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);
});
}
));
exports.restrictedGet = function (req, res, next) {
console.dir('reached restricted get');
var slug = req.params.slug;
console.dir(req.route);
if(req.route.path.indexOf('test')!=-1)
{
namedQuery['testFunction'](req,res,next);
}
else
{
res.status(404).send('no route found. Route requested: ' + req.route.path);
}
// do something with your route here, check what's being appended to the slug and fire off the appropriate function.
};
exports.login = function(req, res, next) {
res.sendfile(serverBase + "/static/public/login.html");
};
exports.logout = function(req, res, next) {
req.session.destroy(function(){
res.redirect('/');
});
};
// this is where you would hook in your passportjs stuff to do hashing of inputted text and compare it to the hash stored in your db etc.
// I use my own simple authentication funciton right now as i'm just testing.
exports.loginPost = function(req, res, next) {
authenticate(req.body.username, req.body.password, function(err, user){
console.log('Reached login user: ', user);
if (user) {
// Regenerate session when signing in
// to prevent fixation
req.session.regenerate(function(){
req.session.user = user;
req.session.success = 'Authenticated as ' + user.name
+ ' click to <a href="/logout">logout</a>. '
+ ' You may now access <a href="/restricted">/restricted</a>.';
res.redirect('/');
});
} else {
req.session.error = 'Authentication failed, please check your '
+ ' username and password.'
+ ' (use "tj" and "foobar")';
res.json({success: false});
res.redirect('/login');
}
});
};
// You could now do this with passport instead:
exports.loginPost = function(req, res, next) {
passport.authenticate('local'), function(err, user){
console.log('Reached login user: ', user);
if (user) {
// Regenerate session when signing in
// to prevent fixation
req.session.regenerate(function(){
req.session.user = user;
req.session.success = 'Authenticated as ' + user.name
+ ' click to <a href="/logout">logout</a>. '
+ ' You may now access <a href="/restricted">/restricted</a>.';
res.redirect('/');
});
} else {
req.session.error = 'Authentication failed, please check your '
+ ' username and password.'
+ ' (use "tj" and "foobar")';
res.json({success: false});
res.redirect('/login');
}
};
};
function authenticate(name, pass, fn) {
var user = { name:name, password: pass }
return fn(null,user);
};
This is where I got alot of my code from: http://www.breezejs.com/samples/zza, http://passportjs.org/guide/authenticate/
Hope this helps!
#EDIT
#I forgot to mention, for the angular side I just have a simple form posting back the values for user and password to the login post endpoint, since the getEndpoint is restricted, the express app will handle the rest of the authentication and restriction side of things for you. If I can be of any further help please don't hesitate to ask.
Here's a very straightforward way to check if a user is logged in.
Assuming you set a middleware like this:
const authMiddleware = (req, res, next) => {
if(req.headers.authorization == undefined) { // or whatever auth strategy you're using
req.isAuthenticated = false;
}
else {
// your own code to fetch user
req.isAuthenticated = true;
}
next();
}
app.use(authMiddleware);
You can create another simple middleware to restrict some routes:
const checkIsAuth = (req, res, next) => {
req.isAuthenticated ? next() : res.sendStatus(401);
}
router.get('/foo', checkIsAuth, fooController.getAll);
I have uploaded an Angular-Express project on github that I have been working on.
It is still work in progress. I hope it helps.
It uses PassportJs for user authentication and is a basic example of server side authorization. It demonstrates how to make API calls accessible only to authenticated users, or only to users with admin role. This is achieved in server/routes.js
calling the middleware functions ensureAuthenticated
, and ensureAdmin
which are defined in server/authentication.js
in routes.js
// anybody can access this
app.get('/api/test/users',
api.testUsers);
// only logged-in users with ADMIN role can access this
app.get('/api/users',
authentication.ensureAdmin,
api.testUsers);
// only logged-in users can access this
app.get('/api/books',
authentication.ensureAuthenticated,
api.books);
in authentication.js
ensureAuthenticated: function(req, res, next) {
if (req.isAuthenticated()) {
return next();
} else {
return res.send(401);
}
},
ensureAdmin: function(req, res, next) {
// ensure authenticated user exists with admin role,
// otherwise send 401 response status
if (req.user && req.user.role == 'ADMIN') {
return next();
} else {
return res.send(401);
}
},