Passwordless authentication: Secure, simple, and fast to deploy

Passwordless is an authentication middleware for Node.js that improves security for your users while being fast and easy to deploy.

The last months were very exciting for everyone interested in web security and privacy: Fantastic articles, discussions, and talks but also plenty of incidents that raised awareness.

Most websites are, however, still stuck with the same authentication mechanism as from the earliest days of the web: username and password.

While username and password have their place, we should be much more challenging if they are the right solution for our projects. We know that most people use the same password on all the sites they visit. For projects without dedicated security experts, should we really open up our users to the risk that a breach of our site also compromises their Amazon account? Also, the classic mechanism has by default at least two attack vectors: the login page and the password recovery page. Especially the latter is often implemented hurried and hence inherently more risky.

We’ve seen quite a bit of great ideas recently and I got particularly excited by one very straightforward and low-tech solution: one-time passwords. They are fast to implement, have a small attack surface, and require neither QR codes nor JavaScript. Whenever a user wants to login or has her session invalidated, she receives a short-lived one-time link with a token via email or text message. If you want to give it a spin, feel free to test the demo on passwordless.net

Unfortunately—depending on your technology stack—there are few to none ready-made solutions out there. Passwordless changes this for Node.js.

Getting started with Node.js & Express

Getting started with Passwordless is straight-forward and you’ll be able to deploy a fully fledged and secure authentication solution for a small project within two hours:

$ npm install passwordless --save

gets you the basic framework. You’ll also want to install one of the existing storage interfaces such as MongoStore which store the tokens securely:

$ npm install passwordless-mongostore --save

To deliver the tokens to the users, email would be the most common option (but text message is also feasible) and you’re free to pick any of the existing email frameworks such as:

$ npm install emailjs --save

Setting up the basics

Let’s require all of the above mentioned modules in the same file that you use to initialise Express:

var passwordless = require('passwordless');
var MongoStore = require('passwordless-mongostore');
var email   = require("emailjs");

If you’ve chosen emailjs for delivery that would also be a great moment to connect it to your email account (e.g. a Gmail account):

var smtpServer  = email.server.connect({
   user:    yourEmail,
   password: yourPwd,
   host:    yourSmtp,
   ssl:     true
});

The final preliminary step would be to tell Passwordless which storage interface you’ve chosen above and to initialise it:

// Your MongoDB TokenStore
var pathToMongoDb = 'mongodb://localhost/passwordless-simple-mail';
passwordless.init(new MongoStore(pathToMongoDb));

Delivering a token

passwordless.addDelivery(deliver) adds a new delivery mechanism. deliver is called whenever a token has to be sent. By default, the mechanism you choose should provide the user with a link in the following format:

http://www.example.com/token={TOKEN}&uid={UID}

deliver will be called with all the needed details. Hence, the delivery of the token (in this case with emailjs) can be as easy as:

passwordless.addDelivery(
    function(tokenToSend, uidToSend, recipient, callback) {
        var host = 'localhost:3000';
        smtpServer.send({
            text:    'Hello!nAccess your account here: http://'
            + host + '?token=' + tokenToSend + '&uid='
            + encodeURIComponent(uidToSend),
            from:    yourEmail,
            to:      recipient,
            subject: 'Token for ' + host
        }, function(err, message) {
            if(err) {
                console.log(err);
            }
            callback(err);
        });
});

Initialising the Express middleware

app.use(passwordless.sessionSupport());
app.use(passwordless.acceptToken({ successRedirect: '/'}));

sessionSupport() makes the login persistent, so the user will stay logged in while browsing your site. Please make sure that you’ve already prepared your session middleware (such as express-session) beforehand.

acceptToken() will intercept any incoming tokens, authenticate users, and redirect them to the correct page. While the option successRedirect is not strictly needed, it is strongly recommended to use it to avoid leaking valid tokens via the referrer header of outgoing HTTP links on your site.

Routing & Authenticating

The following takes for granted that you’ve already setup your router var router = express.Router(); as explained in the express docs

You will need at least two URLs to:

  • Display a page asking for the user’s email
  • Accept the form details (via POST)
/* GET: login screen */
router.get('/login', function(req, res) {
   res.render('login');
});</p>
 
/* POST: login details */
router.post('/sendtoken',
    function(req, res, next) {
        // TODO: Input validation
    },
    // Turn the email address into a user ID
    passwordless.requestToken(
        function(user, delivery, callback) {
            // E.g. if you have a User model:
            User.findUser(email, function(error, user) {
                if(error) {
                    callback(error.toString());
                } else if(user) {
                    // return the user ID to Passwordless
                    callback(null, user.id);
                } else {
                    // If the user couldn’t be found: Create it!
                    // You can also implement a dedicated route
                    // to e.g. capture more user details
                    User.createUser(email, '', '',
                        function(error, user) {
                            if(error) {
                                callback(error.toString());
                            } else {
                                callback(null, user.id);
                            }
                    })
                }
        })
    }),
    function(req, res) {
        // Success! Tell your users that their token is on its way
        res.render('sent');
});

What happens here? passwordless.requestToken(getUserId) has two tasks: Making sure the email address exists and transforming it into a unique user ID that can be sent out via email and can be used for identifying users later on. Usually, you’ll already have a model that is taking care of storing your user details and you can simply interact with it as shown in the example above.

In some cases (think of a blog edited by just a couple of users) you can also skip the user model entirely and just hardwire valid email addresses with their respective IDs:

var users = [
    { id: 1, email: 'marc@example.com' },
    { id: 2, email: 'alice@example.com' }
];
 
/* POST: login details */
router.post('/sendtoken',
    passwordless.requestToken(
        function(user, delivery, callback) {
            for (var i = users.length - 1; i >= 0; i--) {
                if(users[i].email === user.toLowerCase()) {
                    return callback(null, users[i].id);
                }
            }
            callback(null, null);
        }),
        // Same as above…

HTML pages

All it needs is a simple HTML form capturing the user’s email address. By default, Passwordless will look for an input field called user:

<html>
    <body>
        <h1>Login</h1>
        <form action="/sendtoken" method="POST">
            Email:
            <br /><input name="user" type="text">
            <br /><input type="submit" value="Login">
        </form>
    </body>
</html>

Protecting your pages

Passwordless offers middleware to ensure only authenticated users get to see certain pages:

/* Protect a single page */
router.get('/restricted', passwordless.restricted(),
 function(req, res) {
  // render the secret page
});
 
/* Protect a path with all its children */
router.use('/admin', passwordless.restricted());

Who is logged in?

By default, Passwordless makes the user ID available through the request object: req.user. To display or reuse the ID it to pull further details from the database you can do the following:

router.get('/admin', passwordless.restricted(),
    function(req, res) {
        res.render('admin', { user: req.user });
});

Or, more generally, you can add another middleware that pulls the whole user record from your model and makes it available to any route on your site:

app.use(function(req, res, next) {
    if(req.user) {
        User.findById(req.user, function(error, user) {
            res.locals.user = user;
            next();
        });
    } else {
        next();
    }
})

That’s it!

That’s all it takes to let your users authenticate securely and easily. For more details you should check out the deep dive which explains all the options and the example that will show you how to integrate all of the things above into a working solution.

Evaluation

As mentioned earlier, all authentication systems have their tradeoffs and you should pick the right system for your needs. Token-based channels share one risk with the majority of other solutions incl. the classic username/password scheme: If the user’s email account is compromised and/or the channel between your SMTP server and the user’s, the user’s account on your site will be compromised as well. Two default options help mitigate (but not entirely eliminate!) this risk: short-lived tokens and automatic invalidation of the tokens after they’ve been used once.

For most sites token-based authentication represents a step up in security: users don’t have to think of new passwords (which are usually too simple) and there is no risk of users reusing passwords. For us as developers, Passwordless offers a solution that has only one (and simple!) path of authentication that is easier to understand and hence to protect. Also, we don’t have to touch any user passwords.

Another point is usability. We should consider both, the first time usage of your site and the following logons. For first-time users, token-based authentication couldn’t be more straight-forward: They will still have to validate their email address as they have to with classic login mechanisms, but in the best-case scenario there will be no additional details required. No creativity needed to come up with a password that fulfils all restrictions and nothing to memorise. If the user logins again, the experience depends on the specific use case. Most websites have relatively long session timeouts and logins are relatively rare. Or, people’s visits to the website are actually so infrequent that they will have difficulties recounting if they already had an account and if so what the password could have been. In those cases Passwordless presents a clear advantage in terms of usability. Also, there are few steps to take and those can be explained very clearly along the process. Websites that users visit frequently and/or that have conditioned people to login several times a week (think of Amazon) might however benefit from a classic (or even better: two-factor) approach as people will likely be aware of their passwords and there might be more opportunity to convince users about the importance of good passwords.

While Passwordless is considered stable, I would love your comments and contributions on GitHub or your questions on Twitter: @thesumofall

About Florian Heinemann

Florian is a System Design and Management Fellow at MIT focussing on complex socio-technical systems. He worked for several startups in the enterprise software domain before joining Airbus as a Manager for Knowledge and Innovation Management. Florian is passionate about the web, security, tackling complexity, and winter sports.

More articles by Florian Heinemann…

About Robert Nyman [Editor emeritus]

Technical Evangelist & Editor of Mozilla Hacks. Gives talks & blogs about HTML5, JavaScript & the Open Web. Robert is a strong believer in HTML5 and the Open Web and has been working since 1999 with Front End development for the web - in Sweden and in New York City. He regularly also blogs at http://robertnyman.com and loves to travel and meet people.

More articles by Robert Nyman [Editor emeritus]…


38 comments

  1. Alexandru

    Isn’t it more simple to write a token and a small verification code for your users and then all you have to do from page to page is to recheck token.

    You can send it via GET from page to page, and all you have to check is the user ip + how old the token is.

    October 15th, 2014 at 05:41

    1. Florian Heinemann

      This would create security risks due to the token being constantly exposed through referrers: if you leave the website through a click the target website would have your login details. That’s why passwordless automatically invalidates tokens after the first login and redirects to a clean page with a login in the GET query

      October 15th, 2014 at 06:02

      1. Eric Canales

        Also, if you check that the user IP is the same then you lose anybody behind a proxy if their IP changes. Security is not simple. Usually it’s safer to use a verified system.

        October 16th, 2014 at 05:34

  2. Sly

    So, if tokens are sent by email and email is compromised, the whole system is compromised?

    October 15th, 2014 at 06:12

    1. Florian Heinemann

      Yes. This is, however, the same with the classic login system as well: Due to the password reset functionality of those you can easily crack classic systems as soon as you have access to email. The idea is that you have people only remember one complex password rather than a lot of similar / easy passwords for every single site.

      Cheers

      Florian

      October 15th, 2014 at 06:15

      1. David

        Also using two-step authentication in email (plus SSL/TLS SMTP in case of MITM attacks) should help mitigating this even more.

        October 15th, 2014 at 07:08

      2. Harsh

        If an attacker uses the password reset functionality of classic login systems, the user will know that the account was compromised since the old credentials will no longer work but here someone who has broken into the email account can have a permanent backdoor without the user knowing anything.

        October 15th, 2014 at 09:15

        1. Florian Heinemann

          That’s a very fair point, I think. To be honest it would probably still slip through my attention: There are so many site-induced password resets recently due to security breaches + maybe I just forgot my password? Still, good point that might be interesting to dive deeper into!

          October 15th, 2014 at 09:54

        2. Micah Strube

          An added security measure that could be added to mitigate this would be to require a second authentication step (SMS) be used when authenticating an unrecognized device.

          October 16th, 2014 at 05:12

  3. Sean

    I’m intrigued by this idea and think it could be really important. Could someone speak a little bit to a scenario in which users change e-mails? It seems at first glance that there’s no easy way to change an e-mail associated with an account because the token is linked to an e-mail address.

    I suppose in this case, the site could offer a “link additional e-mail to this account” mechanism pretty simply, because upon logging in the e-mail would just be associated with that account. I’m assuming that this would be on the site to implement rather than this middleware?

    October 15th, 2014 at 06:16

    1. Florian Heinemann

      Thanks for the feedback :-) The tokens are just bound to the address for the short timeframe between entering your email address during login and clicking on the link. Hence, you could easily have an interface to change user details on your side – no need for Passwordless to be involved. With the next login Passwordless would lookup the user in your database and validate the new address. Only thing that could happen: If the user deleted their old email address there would be no way anymore to access their account, but I guess this would be a very rare case and could be solved through e.g. an offline process.

      Florian

      October 15th, 2014 at 06:26

      1. mf

        Rare in the case of free accounts, but common in the case of accounts provided by workplaces and ISPs. Maybe a backup delivery address (email, SMS, any other store-and-forward system the user has) should be required at all times to further reduce help desk call volume.

        October 15th, 2014 at 16:43

  4. Lou

    Doesn’t this just pass the buck from a security standpoint? The evaluation points this out by mentioning now that the onus is one the user to ensure that their email account isn’t compromised, nor the path between SMTP servers.

    In the majority of cases, the user has little control or is ignorant of these two factors. They can pick their password, sure, but they don’t have control over where that password is stored and how it’s protected. Same idea with SMTP, unless the contents of the message go through some cryptograpic process in an effort to thwart the plaintext parsing middle-man, sooner or later someone will figure it out.

    October 15th, 2014 at 06:26

    1. Florian Heinemann

      It actually improves security in comparison to classic mechanisms due to reducing the attack vector: also the classic approach has a password reset functionality that is handled via email. In addition you have all the other security benefits mentioned in the article. If you want to avoid email you can always use text message or any other medium.

      Florian

      October 15th, 2014 at 06:44

      1. Lou

        http://en.wikipedia.org/wiki/Short_Message_Service#Vulnerabilities

        Still amounts to passing the buck, imho. But hey, at least the site implementing Passwordless isn’t to blame for other leaky data communication models.

        October 15th, 2014 at 07:26

  5. stefan

    To funnel all websurfing behavior through a trip to the email-box is devious. and dangerous. Besides the fact that you are making money for google (think of all the increased ad revenue that is garnered by ads in free gmailboxes) you also stress all but the largest email providers. This is not a good way to go and not any better than other two factor authentication methods. captcha challenges coupled with a random question/photo from a personalized set would be better. How about this, any time I want a secure website, I give them my instagram feed. They pull a random picture and ask me the day I took it.

    October 15th, 2014 at 06:38

    1. John

      As the day of a random instagram picture… and you say trip to the emailbox is devious. :)

      October 15th, 2014 at 06:58

  6. Sam

    I think this is a great idea. The first time I realized that I could recover my password and take ownership of a site by merely owning the email that I signed up with, I was like.. so why do I need a password?

    I implemented something that I think is very similar I think. I asked the user for an email, hashed it and stored in a table with a unique id. I then sent the user an email with the unique id. The user then clicked on the link and was validated with the table record. The coolness of it was, they could enter the email on one browser and validated it on another, and it all worked.

    One difference is that I then stored the hashed email in local storage and did not ask the user to log in again (unless they wiped local storage, or logged in from a different browser).

    Ive never been good with security, cookies, tokens and such, so I thought it was basically a weak idea and I was just being lazy. Maybe not!

    October 15th, 2014 at 08:17

  7. dc0de

    I think that this is foolish.

    Now you’re just passing on the authentication to an email system. Email systems can be easily subverted and relying upon those for authentication seems short sighted.

    Perhaps this could be better served going to an existing known two factor system, such as Authy, Google Authenticator, or one of the several others.

    October 15th, 2014 at 09:09

    1. Florian Heinemann

      Hey, I think there were a couple of comments pro/con this argument already: It’s more secure than the regular mechanism due to reducing the attack vector. More secure solutions are out there but are also much more complex for the users. This is not intended to protect your Paypal account but to protect less high-risk sites. As a bonus you will increases the security for people’s Paypal account because most people reuse the same password across all sites.

      October 15th, 2014 at 09:52

      1. Bj aka Bjantiques

        In this day and age no one should be using the same password twice, never mind for every site.

        With secure software like RoboForm being available it is easy to create an extremely secure password for each site.

        The Idea of having to check my mail each time is not conducive with working efficiently, nor would be having to wait for an SMS message.
        I would also thing the cost of international SMS services would be a prohibitive factor.

        Bj aka Bjantiques.

        November 1st, 2014 at 13:49

  8. Aaron

    As the owner of a site built in Node.js, I was interested in seeing what you could come up with here, but, I’m left dissapointed. I’m seeing no genuine reason why this is easier. This is akin to a password reset everytime you want to log in. Explain how it’s “more user friendly” to require someone to log in to their email every time they wish to log in to a website? Sending an SMS is no easier either. As I would then have to type out the token into the browser URL bar or end up using my phone to browse the site. I see no benefit to this…

    October 15th, 2014 at 10:16

    1. Florian Heinemann

      Hey! This solution strikes to find a balance between usability and security. The classic solution might be slightly easier (at least if you know your password…) but has huge security issues (see recent news). This solution bumps up security but might be more complicated to use for some use cases. Though, I’m convinced that there are plenty of use scenarios where this one is actually easier to use (think of little frequented sites or sites where people are too bothered to create accounts but have no problem simply providing their email address to e.g. then be able to customize a site / store favorites / …)

      October 15th, 2014 at 10:24

  9. Sean

    @Florian: Something in your last comment struck me — the concept of scenarios. Is there a good and published somewhere of all of the scenarios for logins? I might be unaware of its existence, but I feel that if the community had some guidelines, we might be able to better ascertain what solutions work best in different scenarios. Conceptual guidance might be as important here as toolsets, and the comments I’ve seen here make me think putting together a sort of requirements matrix would be really beneficial for all involved and lead to better discussion around the topic. Not that I’m saying you should have to do this — just that overall I wonder if it would be useful.

    October 15th, 2014 at 13:44

    1. Florian Heinemann

      I think this is a great idea! It would probably greatly enrich the discussion by having people not banging their heads about the question which solution is the best in absolute terms but how we could provide for different sites the best possible balance of security / acceptance / usability / …

      I haven’t seen anything like it yet, but would be more than happy to contribute. If you would want to work on something let me know on @thesumofall

      Cheers

      Florian

      October 15th, 2014 at 13:56

    2. Florian Heinemann

      This might be something: https://twitter.com/fugueish/status/522606311423234049

      October 15th, 2014 at 21:45

  10. Evan Owen

    We’ve been using and promoting password-less authentication at Cotap (https://cotap.com) for over a year now. User response has been overwhelmingly positive, and we love not having the risk of storing passwords. Great to see others promoting this.

    October 15th, 2014 at 20:38

    1. Florian Heinemann

      I think this is a wonderful example of where password-less login makes perfect sense!

      October 15th, 2014 at 20:43

  11. Christopher

    To the author of the article: in the first few paragraphs i noticed multiple grammatical errors. This is an article on mozilla.com. Get your shit together.

    October 15th, 2014 at 22:20

    1. Robert Nyman [Editor]

      Well, you could choose to be constructive about what you believe should be improved or you could be complaining. We would much prefer the respectful approach, and we welcome any good suggestions you might have. Many of the authors for Hacks aren’t native English speakers, and I prefer having them sharing their knowledge, ideas and experiences with the risk of grammatical errors, than not sharing it at all.

      October 16th, 2014 at 00:09

  12. Chris Peterson

    Very cool idea!

    Instead of using a random number, the token could be a Base62-encoded (A-Za-z0-9) HMAC data blob containing the *real* OTP. This would make prettier URLs and the server frontend could reject fake login tokens by inspection, blocking malicious tokens from even getting near your database backend.

    Also, I don’t think the login URL doesn’t need the UID. The session database can map the token to UID.

    October 15th, 2014 at 22:46

    1. Florian Heinemann

      Cool suggestion! If you like feel also more than free to open up issues on GitHub (makes it easier for me to follow up ;-)

      For the UID: Generally, yes. Passwordless was designed with potential session-less use in mind. I doubt that this is really a typical use case so it might indeed be worth to make the passing of the UID optional!

      October 16th, 2014 at 05:35

  13. Noitidart

    “Whenever a user wants to login or has her session invalidated, she receives a short-lived one-time link with a token via email or text message. ”

    LOL

    anyways this is awesome! thanks for the share.

    October 20th, 2014 at 00:41

  14. Caleb

    @ Florian et all.

    Doesn’t a system like this re-enforce “bad” personal security behavior, by asking them to authenticate via a link in e-mail?

    October 20th, 2014 at 11:21

    1. Florian Heinemann

      Hey,

      If you’re concerned about potential phishing attacks you could easily just send out the token via email/sms. People would then have to type it in. Possible but not necessarily the nicest option. So far even the biggest (such as Google) go with the link option but I can see your point

      Florian

      October 20th, 2014 at 15:40

      1. Caleb

        Good point.

        I should have admitted to my “noob” status as a dev. I’m currently getting up to speed on writing secure web stuff. Hope y’all don’t mind a slightly ignorant question.

        October 20th, 2014 at 15:44

        1. Florian Heinemann

          I think it’s a great question and as we can see even the biggest don’t manage to secure their systems.

          October 20th, 2014 at 15:46

  15. Nathan

    What happened to BrowserID/Persona? It looks like it’s still in development, but I hasn’t seen any news about it lately. It’s all about Firefox Accounts now which is a step backwards.

    November 7th, 2014 at 09:31

Comments are closed for this article.