user authentication libraries for node.js?
Asked Answered
P

15

286

Are there any existing user authentication libraries for node.js? In particular I'm looking for something that can do password authentication for a user (using a custom backend auth DB), and associate that user with a session.

Before I wrote an auth library, I figured I would see if folks knew of existing libraries. Couldn't find anything obvious via a google search.

-Shreyas

Permissible answered 16/8, 2010 at 22:44 Comment(1)
For searchability: Something equivalent to omniauth (rails) or python social-auth. PHP (and other common web server languages) users should feel free to add their equivalent as well.Unmannerly
P
28

Looks like the connect-auth plugin to the connect middleware is exactly what I need

I'm using express [ http://expressjs.com ] so the connect plugin fits in very nicely since express is subclassed (ok - prototyped) from connect

Permissible answered 22/8, 2010 at 6:44 Comment(3)
hey, do you have an example of what you did? simply requiring connect-auth and calling “.authenticate” on “req” returns “TypeError: Object # has no method 'authenticate'“ for me.Kaplan
IMHO This Plugin is way to heavy for simple http authentificationRoderick
And this plugin works against the connect onion ring architectureRoderick
J
239

If you are looking for an authentication framework for Connect or Express, Passport is worth investigating: https://github.com/jaredhanson/passport

(Disclosure: I'm the developer of Passport)

I developed Passport after investigating both connect-auth and everyauth. While they are both great modules, they didn't suit my needs. I wanted something that was more light-weight and unobtrusive.

Passport is broken down into separate modules, so you can choose to use only what you need (OAuth, only if necessary). Passport also does not mount any routes in your application, giving you the flexibility to decide when and where you want authentication, and hooks to control what happens when authentication succeeds or fails.

For example, here is the two-step process to setup form-based (username and password) authentication:

passport.use(new LocalStrategy(
  function(username, password, done) {
    // Find the user from your DB (MongoDB, CouchDB, other...)
    User.findOne({ username: username, password: password }, function (err, user) {
      done(err, user);
    });
  }
));

app.post('/login', 
  passport.authenticate('local', { failureRedirect: '/login' }),
  function(req, res) {
    // Authentication successful. Redirect home.
    res.redirect('/');
  });

Additional strategies are available for authentication via Facebook, Twitter, etc. Custom strategies can be plugged-in, if necessary.

Jacey answered 24/10, 2011 at 17:34 Comment(5)
Among all auth packages for node I selected passport. It's well-documented and easy to use, and supports more strategies.Scatterbrain
I'm using passport at the moment for a prototype, and I don't recommend it because it seems not maintained, and the design is not very good. For example, it forces you to use connect-flash when it could simply use req.session.messages, and the passport-google advertised on the website is outdated since it uses Google OpenId that is deprecated, and there is no link to passport-google-oauth that should replace it. Also this is the signature of a callback after authentification:done(null,false,{ message:'Incorrect username.' }) it's terrible since we don't know what all those parameters are.Discontinue
@Discontinue I need to update the docs to point to the new authentication methods that Google now prefers. As you mention, support exists for those and they work well. As for design questions, passport does not force you to use connect-flash, and the arguments you mention are documented on the guide. If you need help understanding, there are forums where people can assist and answer your questions.Jacey
Not for nothing - but I just finished plugging Passport (used the supplied example). Super easy! I realize it's been a few years since since the most recent comments. I would recommend anybody take a look.Demmy
can I use passport js to create my own authentication service and use the service that I've created for my ASP .net core app and others?Mudd
A
92

Session + If

I guess the reason that you haven't found many good libraries is that using a library for authentication is mostly over engineered.

What you are looking for is just a session-binder :) A session with:

if login and user == xxx and pwd == xxx 
   then store an authenticated=true into the session 
if logout destroy session

thats it.


I disagree with your conclusion that the connect-auth plugin is the way to go.

I'm using also connect but I do not use connect-auth for two reasons:

  1. IMHO breaks connect-auth the very powerful and easy to read onion-ring architecture of connect. A no-go - my opinion :). You can find a very good and short article about how connect works and the onion ring idea here.

  2. If you - as written - just want to use a basic or http login with database or file. Connect-auth is way too big. It's more for stuff like OAuth 1.0, OAuth 2.0 & Co


A very simple authentication with connect

(It's complete. Just execute it for testing but if you want to use it in production, make sure to use https) (And to be REST-Principle-Compliant you should use a POST-Request instead of a GET-Request b/c you change a state :)

var connect = require('connect');
var urlparser = require('url');

var authCheck = function (req, res, next) {
    url = req.urlp = urlparser.parse(req.url, true);

    // ####
    // Logout
    if ( url.pathname == "/logout" ) {
      req.session.destroy();
    }

    // ####
    // Is User already validated?
    if (req.session && req.session.auth == true) {
      next(); // stop here and pass to the next onion ring of connect
      return;
    }

    // ########
    // Auth - Replace this example with your Database, Auth-File or other things
    // If Database, you need a Async callback...
    if ( url.pathname == "/login" && 
         url.query.name == "max" && 
         url.query.pwd == "herewego"  ) {
      req.session.auth = true;
      next();
      return;
    }

    // ####
    // This user is not authorized. Stop talking to him.
    res.writeHead(403);
    res.end('Sorry you are not authorized.\n\nFor a login use: /login?name=max&pwd=herewego');
    return;
}

var helloWorldContent = function (req, res, next) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('authorized. Walk around :) or use /logout to leave\n\nYou are currently at '+req.urlp.pathname);
}

var server = connect.createServer(
      connect.logger({ format: ':method :url' }),
      connect.cookieParser(),
      connect.session({ secret: 'foobar' }),
      connect.bodyParser(),
      authCheck,
      helloWorldContent
);

server.listen(3000);

NOTE

I wrote this statement over a year ago and have currently no active node projects. So there are may be API-Changes in Express. Please add a comment if I should change anything.

Angelynanger answered 16/8, 2010 at 22:44 Comment(11)
Why does connect-auth break the onion/layers pattern? is it because it doesn't use next()? Could it?Freemon
Yes. It must use next() because thats the idea behind connect. Connect has a layer-architecture / form of code structure. And every layer has the power to stop the request execution by not calling next(). If we are talking about authentication: An authentication layer will check if the user has the correct permissions. If everything is fine the layer calls next(). If not this auth-layer generates an error and will not call next().Roderick
man, this is exactly what I was looking for. connect-auth was giving me a bit of indigestion. I just logged into my app for the first time. thanks so much.Shawannashawl
This still doesn't help to answer how to connect to a database backend (preferably with encrypted passwords). I appreciate your comment that this one library is over-engineered, but surely there is one that isn't. Also, if I wanted to write my own auth system I would have used Struts in Java. just like the OP, I want to know which plugins will do that for me in 1 line of code.Dreiser
great answer Nivoc. Doesn't work with latest versions of connect tho. I had to change... cookieDecoder() --> cookieParser() and bodyDecoder() --> bodyParser() and remove the next() call from helloWorldContent function as i was getting an error 'Can't set headers after they are sent'Montmartre
@Angelynanger can you update the code without using connect module , because using connect module need to give all series function in , You can check my code here in this question #7991390, please help want to build login moduleIra
I'm using the latest version of express and this worked out of the box (renaming my var express = require('express') to var connect = require('express'). Playing with the code totally helped me grasp the onion concept. Thanks!Comeuppance
@nivoc, Isnt this example expecting to find username and password from the url itself? That is bad, right? Moreover, if authentication is used as a middleware, then user might not be able to view any page on the site,not even the login page.Terramycin
@KamalReddy I think this is really just meant as an illustrative example of what one could do. Not the be-all-end-all solution.Chestnut
@KamalReddy You are right. Following the REST-Principle, you should use a POST-Request for the login. And in a production env. https is a must!Roderick
WRT the first example psuedo-code....'true' is not a very good auth token. Predictability is guaranteed. You might have been oversimplifying to make a point. But making good, unpredictable tokens would be important, as is a mechanism for tying the token to the correct user.Disturb
P
28

Looks like the connect-auth plugin to the connect middleware is exactly what I need

I'm using express [ http://expressjs.com ] so the connect plugin fits in very nicely since express is subclassed (ok - prototyped) from connect

Permissible answered 22/8, 2010 at 6:44 Comment(3)
hey, do you have an example of what you did? simply requiring connect-auth and calling “.authenticate” on “req” returns “TypeError: Object # has no method 'authenticate'“ for me.Kaplan
IMHO This Plugin is way to heavy for simple http authentificationRoderick
And this plugin works against the connect onion ring architectureRoderick
O
16

I was basically looking for the same thing. Specifically, I wanted the following:

  1. To use express.js, which wraps Connect's middleware capability
  2. "Form based" authentication
  3. Granular control over which routes are authenticated
  4. A database back-end for users/passwords
  5. Use sessions

What I ended up doing was creating my own middleware function check_auth that I pass as an argument to each route I want authenticated. check_auth merely checks the session and if the user is not logged in, then redirects them to the login page, like so:

function check_auth(req, res, next) {

  //  if the user isn't logged in, redirect them to a login page
  if(!req.session.login) {
    res.redirect("/login");
    return; // the buck stops here... we do not call next(), because
            // we don't want to proceed; instead we want to show a login page
  }

  //  the user is logged in, so call next()
  next();
}

Then for each route, I ensure this function is passed as middleware. For example:

app.get('/tasks', check_auth, function(req, res) {
    // snip
});

Finally, we need to actually handle the login process. This is straightforward:

app.get('/login', function(req, res) {
  res.render("login", {layout:false});
});

app.post('/login', function(req, res) {

  // here, I'm using mongoose.js to search for the user in mongodb
  var user_query = UserModel.findOne({email:req.body.email}, function(err, user){
    if(err) {
      res.render("login", {layout:false, locals:{ error:err } });
      return;
    }

    if(!user || user.password != req.body.password) {
      res.render("login",
        {layout:false,
          locals:{ error:"Invalid login!", email:req.body.email }
        }
      );
    } else {
      // successful login; store the session info
      req.session.login = req.body.email;
      res.redirect("/");
    }
  });
});

At any rate, this approach was mostly designed to be flexible and simple. I'm sure there are numerous ways to improve it. If you have any, I'd very much like your feedback.

EDIT: This is a simplified example. In a production system, you'd never want to store & compare passwords in plain text. As a commenter points out, there are libs that can help manage password security.

Orcinol answered 27/3, 2012 at 2:19 Comment(1)
this is good, except you should use bcrypt for storing password (not plain text in db). There's a good post here about it: devsmash.com/blog/…Mansion
P
14

Also have a look at everyauth if you want third party/social network login integration.

Painful answered 8/9, 2011 at 15:50 Comment(0)
H
8

Here is some code for basic authentication from one of my projects. I use it against CouchDB with and additional auth data cache, but I stripped that code.

Wrap an authentication method around you request handling, and provide a second callback for unsuccessfull authentication. The success callback will get the username as an additional parameter. Don't forget to correctly handle requests with wrong or missing credentials in the failure callback:

/**
 * Authenticate a request against this authentication instance.
 * 
 * @param request
 * @param failureCallback
 * @param successCallback
 * @return
 */
Auth.prototype.authenticate = function(request, failureCallback, successCallback)
{
    var requestUsername = "";
    var requestPassword = "";
    if (!request.headers['authorization'])
    {
        failureCallback();
    }
    else
    {
        var auth = this._decodeBase64(request.headers['authorization']);
        if (auth)
        {
            requestUsername = auth.username;
            requestPassword = auth.password;
        }
        else
        {
            failureCallback();
        }
    }


    //TODO: Query your database (don't forget to do so async)


    db.query( function(result)
    {
        if (result.username == requestUsername && result.password == requestPassword)
        {
            successCallback(requestUsername);
        }
        else
        {
            failureCallback();
        }
    });

};


/**
 * Internal method for extracting username and password out of a Basic
 * Authentication header field.
 * 
 * @param headerValue
 * @return
 */
Auth.prototype._decodeBase64 = function(headerValue)
{
    var value;
    if (value = headerValue.match("^Basic\\s([A-Za-z0-9+/=]+)$"))
    {
        var auth = (new Buffer(value[1] || "", "base64")).toString("ascii");
        return {
            username : auth.slice(0, auth.indexOf(':')),
            password : auth.slice(auth.indexOf(':') + 1, auth.length)
        };
    }
    else
    {
        return null;
    }

};
Highflier answered 18/8, 2010 at 7:59 Comment(1)
I wanted to avoid basic auth in favor of form-based auth. This is definitely an elegant solution to the basic auth problem. I think I may have found a good auth framework though (connect-auth - sits on top of connectjs)Permissible
B
4

A few years have passed and I'd like to introduce my authentication solution for Express. It's called Lockit. You can find the project on GitHub and a short intro at my blog.

So what are the differences to the existing solutions?

  • easy to use: set up your DB, npm install, require('lockit'), lockit(app), done
  • routes already built-in (/signup, /login, /forgot-password, etc.)
  • views already built-in (based on Bootstrap but you can easily use your own views)
  • it supports JSON communication for your AngularJS / Ember.js single page apps
  • it does NOT support OAuth and OpenID. Only username and password.
  • it works with several databases (CouchDB, MongoDB, SQL) out of the box
  • it has tests (I couldn't find any tests for Drywall)
  • it is actively maintained (compared to everyauth)
  • email verification and forgot password process (send email with token, not supported by Passport)
  • modularity: use only what you need
  • flexibility: customize all the things

Take a look at the examples.

Bedtime answered 3/2, 2014 at 8:53 Comment(0)
M
4

A different take on authentication is Passwordless, a token-based authentication module for express that circumvents the inherent problem of passwords [1]. It's fast to implement, doesn't require too many forms, and offers better security for the average user (full disclosure: I'm the author).

[1]: Passwords are Obsolete

Milstone answered 29/6, 2014 at 11:42 Comment(0)
W
4

A word of caution regarding handrolled approaches:

I'm disappointed to see that some of the suggested code examples in this post do not protect against such fundamental authentication vulnerabilities such as session fixation or timing attacks.

Contrary to several suggestions here, authentication is not simple and handrolling a solution is not always trivial. I would recommend passportjs and bcrypt.

If you do decide to handroll a solution however, have a look at the express js provided example for inspiration.

Good luck.

Widgeon answered 14/7, 2020 at 3:47 Comment(0)
C
2

There is a project called Drywall that implements a user login system with Passport and also has a user management admin panel. If you're looking for a fully-featured user authentication and management system similar to something like what Django has but for Node.js, this is it. I found it to be a really good starting point for building a node app that required a user authentication and management system. See Jared Hanson's answer for information on how Passport works.

Citify answered 8/11, 2013 at 23:8 Comment(0)
P
2

Here are two popular Github libraries for node js authentication:

https://github.com/jaredhanson/passport ( suggestible )

https://nodejsmodules.org/pkg/everyauth

Purebred answered 21/3, 2014 at 17:2 Comment(0)
G
1

Quick simple example using mongo, for an API that provides user auth for ie Angular client

in app.js

var express = require('express');
var MongoStore = require('connect-mongo')(express);

// ...

app.use(express.cookieParser());
// obviously change db settings to suit
app.use(express.session({
    secret: 'blah1234',
    store: new MongoStore({
        db: 'dbname',
        host: 'localhost',
        port: 27017
    })
}));

app.use(app.router);

for your route something like this:

// (mongo connection stuff)

exports.login = function(req, res) {

    var email = req.body.email;
    // use bcrypt in production for password hashing
    var password = req.body.password;

    db.collection('users', function(err, collection) {
        collection.findOne({'email': email, 'password': password}, function(err, user) {
            if (err) {
                res.send(500);
            } else {
                if(user !== null) {
                    req.session.user = user;
                    res.send(200);
                } else {
                    res.send(401);
                }
            }
        });
    });
};

Then in your routes that require auth you can just check for the user session:

if (!req.session.user) {
    res.send(403);
}
Gobi answered 11/3, 2014 at 9:43 Comment(0)
L
0

Here is a new authentication library that uses timestamped tokens. The tokens can be emailed or texted to users without the need to store them in a database. It can be used for passwordless authentication or for two-factor authentication.

https://github.com/vote539/easy-no-password

Disclosure: I am the developer of this library.

Luteous answered 22/8, 2016 at 6:59 Comment(0)
H
0

If you need authentication with SSO (Single Sign On) with Microsoft Windows user account. You may give a try to https://github.com/jlguenego/node-expose-sspi.

It will give you a req.sso object which contains all client user information (login, display name, sid, groups).

const express = require("express");
const { sso, sspi } = require("node-expose-sspi");

sso.config.debug = false;

const app = express();

app.use(sso.auth());

app.use((req, res, next) => {
  res.json({
    sso: req.sso
  });
});

app.listen(3000, () => console.log("Server started on port 3000"));

Disclaimer: I am the author of node-expose-sspi.

Hey answered 5/3, 2020 at 18:43 Comment(0)
P
0

slim-auth

A lightweight, zero-configuration user authentication module. It doesn't need a sperate database.

https://www.npmjs.com/package/slimauth

It's simple as:

app.get('/private-page', (req, res) => {

    if (req.user.isAuthorized) {
        // user is logged in! send the requested page
        // you can access req.user.email
    }
    else {
        // user not logged in. redirect to login page
    }
})
Panhandle answered 17/3, 2020 at 5:46 Comment(1)
module is deprecated.Verona

© 2022 - 2024 — McMap. All rights reserved.