16

Is there an optional authentication middleware from Passport.js?

Let's say I have a route, /api/users. I want to give just a list of users to the public, but to authenticated people, I want to add more fields.

Currently I have just a dumb custom method that does the same thing, but I wonder if:

  • Passport.js already provides such thing or
  • how can I make this a part of passport, like a plugin or so.

My method, roughly, looks like

function optionalAuth(req, res, next) {

    var authHeader = req.headers.authorization;
    var token = parseToken(authHeader); // just getting the OAuth token here
    if(!token) {

        return next();
    }
    User.findOne({
        token: token
    }, function(err, user) {

        if(err) {
            return res.json(401, {message: 'auth expired'});
        };
        if(user) {
            req.user = user;
        }
        next();
    });
}

This, however, seems dumb to me, and also not in passport-auth-strategies.js or some other auth layer where I think it should be. What is the better way to do it?

Bonus points for telling me if I'm doing the proper thing returning 401 if I find a token but it's invalid :)

Zlatko
  • 18,936
  • 14
  • 70
  • 123
  • Assuming that you have the basics working (authenticating users), I think you can use `req.isAuthenticated()` (which is something that Passport defines) to check if you want to return the extra fields or not. – robertklep Sep 12 '14 at 12:42
  • 1
    I can't use req.isAuthenticated because I can't put passport.authenticate() before this. Why? Because non-authenticated users would get rejected. I don't have a session, it's a stateless API, just a bearer token. If I put passport.authenticate() in the route, some users would get a 401. I want them to pass, only get limited info. – Zlatko Sep 14 '14 at 08:26

2 Answers2

17

Might be late now, but there's an anonymous Passport strategy to allow exactly this. That way the public routes can either take authentication or not, but when they do you'll still have all of the information associated with the authenticated user. Check it out here: https://github.com/jaredhanson/passport-anonymous

Ian Storm Taylor
  • 8,520
  • 12
  • 55
  • 72
15

Here's a simple PoC:

var express       = require('express');
var app           = express();
var server        = app.listen(3012);
var passport      = require('passport');
var LocalStrategy = require('passport-local').Strategy;

app.use(passport.initialize());

passport.use(new LocalStrategy(function(username, password, done) {
  if (username === 'foo' && password === 'bar') {
    return done(null, { username : 'foo' });
  }
  return done(null, false);
}));

app.get('/api', function(req, res, next) {
  passport.authenticate('local', function(err, user, info) {
    var data = { hello : 'world' };
    // Only if the user authenticated properly do we include secret data.
    if (user) {
      data.secret = '3133753CR37';
    }
    return res.send(data);
  })(req, res, next);
});

It's calling passport.authenticate 'manually' in the /api endpoint. That way, you get more control over how to deal with authentication error (which—in your situation—shouldn't be treated as errors but as a way of limiting the output).

Here's the output without proper authentication:

$ curl 'localhost:3012/api?username=foo&password=wrong'
{"hello":"world"}

And here's with:

$ curl 'localhost:3012/api?username=foo&password=bar'
{"hello":"world","secret":"3133753CR37"}

To use as a middleware:

var middleware = function(req, res, next) {
  passport.authenticate('local', function(err, user, info) {
    req.authenticated = !! user;
    next();
  })(req, res, next);
};

app.get('/api', middleware, function(req, res, next) {
  var data = { hello : 'world' };
  if (req.authenticated) {
    data.secret = '3133753CR37';
  }
  return res.send(data);
});
robertklep
  • 198,204
  • 35
  • 394
  • 381
  • Thanks. In a way it is better then my attempt, as it uses passport for auth. But it's a bit more clumsy, I can't just put it as a middleware on the unauthenticated routes. But this might be wrappable into some sort of a plugin. I'll have to try though. – Zlatko Sep 14 '14 at 13:46
  • @Zlatko I edited my answer to give an idea on how you could turn it into a middleware. – robertklep Sep 15 '14 at 05:25
  • Awesome, that's it, better then my meddling. – Zlatko Sep 15 '14 at 09:16