Git Product home page Git Product logo

passwordless's Introduction

Passwordless

Passwordless is a modern node.js module for Express that allows authentication and authorization without passwords by simply sending one-time password (OTPW) tokens via email or other means. It utilizes a very similar mechanism as the reset password feature of classic websites. The module was inspired by Justin Balthrop's article "Passwords are Obsolete"

Token-based authentication is...

  • Faster to implement compared to typical user auth systems (you only need one form)
  • Better for your users as they get started with your app quickly and don't have to remember passwords
  • More secure for your users avoiding the risks of reused passwords

Getting you started

The following should provide a quick-start in using Passwordless. If you need more details check out the example, the deep dive, or the documentation. Also, don't hesitate to raise comments and questions on GitHub.

1. Install the module:

$ npm install passwordless --save

You'll also want to install a TokenStore such as MongoStore and something to deliver the tokens (be it email, SMS or any other means). For example:

$ npm install passwordless-mongostore --save

$ npm install emailjs --save

If you need to store your tokens differently consider developing a new TokenStore and let us know.

2. Require the needed modules

You will need:

  • Passwordless
  • A TokenStore to store the tokens such as MongoStore
  • Something to deliver the tokens such as emailjs for email or twilio for text messages / SMS
var passwordless = require('passwordless');
var MongoStore = require('passwordless-mongostore');
var email   = require('emailjs');

3. Setup your delivery

This is very much depending on how you want to deliver your tokens, but if you use emailjs this could look like this:

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

4. Initialize Passwordless

passwordless.init() will take your TokenStore, which will store the generated tokens as shown below for a MongoStore:

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

5. Tell Passwordless how to deliver 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}

That's how you could do this with emailjs:

// Set up a delivery service
passwordless.addDelivery(
	function(tokenToSend, uidToSend, recipient, callback, req) {
		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);
		});
});

6. Setup the middleware for express

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. Make sure to have added your session middleware before this line. Have a look at express-session how to setup sessions if you are unsure. Please be aware: If you decide to use cookie-session rather than e.g. express-session as your middleware you have to set passwordless.init(tokenStore, {skipForceSessionSave:true})

acceptToken() will accept incoming tokens and authenticate the user (see the URL in step 5). 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. When provided, the user will be forwarded to the given URL as soon as she has been authenticated.

Instead of accepting tokens on any URL as done above you can also restrict the acceptance of tokens to certain URLs:

// Accept tokens only on /logged_in (be sure to change the
// URL you deliver in step 5)
router.get('/logged_in', passwordless.acceptToken(), 
	function(req, res) {
		res.render('homepage');
});

7. The router

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 URLs to:

  • Display a page asking for the user's email (or phone number, ...)
  • Receive these details (via POST) and identify the user

For example like this:

/* GET login screen. */
router.get('/login', function(req, res) {
   res.render('login');
});

/* POST login details. */
router.post('/sendtoken', 
	passwordless.requestToken(
		// Turn the email address into an user's ID
		function(user, delivery, callback, req) {
			// usually you would want something like:
			User.find({email: user}, callback(ret) {
			   if(ret)
			      callback(null, ret.id)
			   else
			      callback(null, null)
	      })
	      // but you could also do the following 
	      // if you want to allow anyone:
	      // callback(null, user);
		}),
	function(req, res) {
	   // success!
  		res.render('sent');
});

What happens here? passwordless.requestToken(getUserId) has two tasks: Making sure the email address exists and transforming it into a proper user ID that will become the identifier from now on. For example [email protected] becomes 123 or 'u1002'. You call callback(null, ID) if all is good, callback(null, null) if you don't know this email address, and callback('error', null) if something went wrong. At this stage, please make sure that you've added middleware to parse POST data (such as body-parser).

Most likely, you want a user registration page where you take an email address and any other user details and generate an ID. However, you can also simply accept any email address by skipping the lookup and just calling callback(null, user).

In an even simpler scenario and if you just have a fixed list of users do the following:

// GET login as above

var users = [
	{ id: 1, email: '[email protected]' },
	{ id: 2, email: '[email protected]' }
];

/* 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);
		}),
		function(req, res) {
			// success!
		res.render('sent');
});

8. Login page

All you need is a form where users enter their email address, for example:

<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>

By default, Passwordless will look for a field called user submitted via POST.

9. Protect your pages

You can protect all pages that should only be accessed by authenticated users by using the passwordless.restricted() middleware, for example:

/* GET restricted site. */
router.get('/restricted', passwordless.restricted(),
 function(req, res) {
  // render the secret page
});

You can also protect a full path, by adding:

router.use('/admin', passwordless.restricted());

10. Who is logged in?

Passwordless stores the user ID in req.user (this can be changed via configuration). So, if you want to display the user's details or use them for further requests, do something like:

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

You could also create a middleware that is adding the user to any request and enriching it with all user details. Make sure, though, that you are adding this middleware after acceptToken() and sessionSupport():

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

Common options

Logout

Just call passwordless.logout() as in:

router.get('/logout', passwordless.logout(),
	function(req, res) {
		res.redirect('/');
});

Redirects

Redirect non-authorised users who try to access protected resources with failureRedirect (default is a 401 error page):

router.get('/restricted', 
	passwordless.restricted({ failureRedirect: '/login' });

Redirect unsuccessful login attempts with failureRedirect (default is a 401 or 400 error page):

router.post('/login', 
	passwordless.requestToken(function(user, delivery, callback) {
		// identify user
}, { failureRedirect: '/login' }),
	function(req, res){
		// success
});

After the successful authentication through acceptToken(), you can redirect the user to a specific URL with successRedirect:

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

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. When provided, the user will be forwarded to the given URL as soon as she has been authenticated. If not provided, Passwordless will simply call the next middleware.

Error flashes

Error flashes are session-based error messages that are pushed to the user with the next request. For example, you might want to show a certain message when the user authentication was not successful or when a user was redirected after accessing a resource she should not have access to. To make this work, you need to have sessions enabled and a flash middleware such as connect-flash installed.

Error flashes are supported in any middleware of Passwordless that supports failureRedirect (see above) but only(!) if failureRedirect is also supplied:

  • restricted() when the user is not authorized to access the resource
  • requestToken() when the supplied user details are unknown

As an example:

router.post('/login', 
	passwordless.requestToken(function(user, delivery, callback) {
		// identify user
}, { failureRedirect: '/login', failureFlash: 'This user is unknown!' }),
	function(req, res){
		// success
});

The error flashes are pushed onto the passwordless array of your flash middleware. Check out the connect-flash docs how to pull the error messages, but a typical scenario should look like this:

router.get('/mistake',
	function(req, res) {
		var errors = req.flash('passwordless'), errHtml;
		for (var i = errors.length - 1; i >= 0; i--) {
			errHtml += '<p>' + errors[i] + '</p>';
		}
		res.send(200, errHtml);
});

Success flashes

Similar to error flashes success flashes are session-based messages that are pushed to the user with the next request. For example, you might want to show a certain message when the user has clicked on the token URL and the token was accepted by the system. To make this work, you need to have sessions enabled and a flash middleware such as connect-flash installed.

Success flashes are supported by the following middleware of Passwordless:

  • acceptToken() when the token was successfully validated
  • logout() when the user was logged in and was successfully logged out
  • requestToken() when the token was successfully stored and send out to the user

Consider the following example:

router.get('/logout', passwordless.logout( 
	{successFlash: 'Hope to see you soon!'} ),
	function(req, res) {
  	res.redirect('/home');
});

The messages are pushed onto the passwordless-success array of your flash middleware. Check out the connect-flash docs how to pull the messages, but a typical scenario should look like this:

router.get('/home',
	function(req, res) {
		var successes = req.flash('passwordless-success'), html;
		for (var i = successes.length - 1; i >= 0; i--) {
			html += '<p>' + successes[i] + '</p>';
		}
		res.send(200, html);
});

2-step authentication (e.g. for SMS)

For some token-delivery channels you want to have the shortest possible token (e.g. for text messages). One way to do so is to remove the user ID from the token URL and to only keep the token for itself. The user ID is then kept in the session. In practice his could look like this: A user types in his phone number, hits submit, is redirected to another page where she has to type in the token received per SMS, and then hit submit another time.

To achieve this, requestToken stores the requested UID in req.passwordless.uidToAuth. Putting it all together, take the following steps:

1: Read out req.passwordless.uidToAuth

// Display a new form after the user has submitted the phone number
router.post('/sendtoken', passwordless.requestToken(function(...) { },
	function(req, res) {
  	res.render('secondstep', { uid: req.passwordless.uidToAuth });
});

2: Display another form to submit the token submitting the UID in a hidden input

<html>
	<body>
		<h1>Login</h1>
		<p>You should have received a token via SMS. Type it in below:</p>
		<form action="/auth" method="POST">
			Token:
			<br><input name="token" type="text">
			<input type="hidden" name="uid" value="<%= uid %>">
			<br><input type="submit" value="Login">
		</form>
	</body>
</html>

3: Allow POST to accept tokens

router.post('/auth', passwordless.acceptToken({ allowPost: true }),
	function(req, res) {
		// success!
});

Successful login and redirect to origin

Passwordless supports the redirect of users to the login page, remembering the original URL, and then redirecting them again to the originally requested page as soon as the token has been accepted. Due to the many steps involved, several modifications have to be undertaken:

1: Set originField and failureRedirect for passwordless.restricted()

Doing this will call /login with /login?origin=/admin to allow later reuse

router.get('/admin', passwordless.restricted( 
	{ originField: 'origin', failureRedirect: '/login' }));

2: Display origin as hidden field on the login page

Be sure to pass origin to the page renderer.

<form action="/sendtoken" method="POST">
	Token:
	<br><input name="token" type="text">
	<input type="hidden" name="origin" value="<%= origin %>">
	<br><input type="submit" value="Login">
</form>

3: Let requestToken() accept origin

This will store the original URL next to the token in the TokenStore.

app.post('/sendtoken', passwordless.requestToken(function(...) { }, 
	{ originField: 'origin' }),
	function(req, res){
		// successfully sent
});

4: Reconfigure acceptToken() middleware

app.use(passwordless.acceptToken( { enableOriginRedirect: true } ));

Several delivery strategies

In case you want to use several ways to send out tokens you have to add several delivery strategies to Passwordless as shown below:

passwordless.addDelivery('email', 
	function(tokenToSend, uidToSend, recipient, callback) {
		// send the token to recipient
});
passwordless.addDelivery('sms', 
	function(tokenToSend, uidToSend, recipient, callback) {
		// send the token to recipient
});

To simplify your code, provide the field delivery to your HTML page which submits the recipient details. Afterwards, requestToken() will allow you to distinguish between the different methods:

router.post('/sendtoken', 
	passwordless.requestToken(
		function(user, delivery, callback) {
			if(delivery === 'sms')
				// lookup phone number
			else if(delivery === 'email')
				// lookup email
		}),
	function(req, res) {
  		res.render('sent');
});

Modify lifetime of a token

This is particularly useful if you use shorter tokens than the default to keep security on a high level:

// Lifetime in ms for the specific delivery strategy
passwordless.addDelivery(
	function(tokenToSend, uidToSend, recipient, callback) {
		// send the token to recipient
}, { ttl: 1000*60*10 });

Allow token reuse

By default, all tokens are invalidated after they have been used by the user. Should a user try to use the same token again and is not yet logged in, she will not be authenticated. In some cases (e.g. stateless operation or increased convenience) you might want to allow the reuse of tokens. Please be aware that this might open up your users to the risk of valid tokens being used by third parties without the user being aware of it.

To enable the reuse of tokens call init() with the option allowTokenReuse: true, as shown here:

passwordless.init(new TokenStore(), 
	{ allowTokenReuse: true });

Different tokens

You can generate your own tokens. This is not recommended except you face delivery constraints such as SMS-based authentication. If you reduce the complexity of the token, please consider reducing as well the lifetime of the token (see above):

passwordless.addDelivery(
	function(tokenToSend, uidToSend, recipient, callback) {
		// send the token to recipient
}, {tokenAlgorithm: function() {return 'random'}});

Stateless operation

Just remove the app.use(passwordless.sessionSupport()); middleware. Every request for a restricted resource has then to be combined with a token and uid. You should consider the following points:

  • By default, tokens are invalidated after their first use. For stateless operations you should call passwordless.init() with the following option: passwordless.init(tokenStore, {allowTokenReuse:true}) (for details see above)
  • Tokens have a limited lifetime. Consider extending it (for details see above), but be aware about the involved security risks
  • Consider switching off redirects such as successRedirect on the acceptToken() middleware

The tokens and security

By default, tokens are generated using 16 Bytes of pseudo-random data as produced by the cryptographically strong crypto library of Node.js. This can be considered strong enough to withstand brute force attacks especially when combined with a finite time-to-live (set by default to 1h). In addition, it is absolutely mandatory to store the tokens securely by hashing and salting them (done by default with TokenStores such as MongoStore). Security can be further enhanced by limiting the number of tries per user ID before locking that user out from the service for a certain amount of time.

Further documentation

Tests

Download the whole repository and call: $ npm test

License

MIT License

Author

Florian Heinemann @thesumofall

passwordless's People

Contributors

abv avatar briandela avatar florianheinemann avatar lloydcotten avatar nnarhinen avatar strictd avatar z38 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

passwordless's Issues

End-user friendly instruction

Hi,

I am just adapting to the passwordless technique in a new app and I really like the procedure, good work here!
However, some of test users requested explanation and were asking questions, e.g. "Can I delete the login mail after usage" etc. I am therefore looking for a end-user friendly (perhaps illustrated), short and simple introduction into passwordless logins to include at the login page/send mails.
However, rather than doing it for my app alone, wouldn't it be a good thing to collaborate on something like that under a neutral public address (e.g. how-to-signin.passwordless.net) where everybody building such applications can link to?

What do you think?

delivery callback could take current page as origin

Apologies if I'm missing something obvious, but I've read the docs several times and done lots of testing, and I just don't see how to do what I want.

I have various paths set up in the app, all of which are visible to all but modifiable only by certain logged-in users. If a user is on a particular page, enters an email, and hits "login" (this AJAX form is on every page), I'd like for the callback passed to addDelivery() to be able to include this origin path in the email it sends, so that clicking that link brings the user back to the same page, except now in logged-in state. I'm indifferent to whether that link looks like /my-dir/my-page/?token=... or /?origin=my-dir%2Fmy-page&token=.... (Although the former would probably work with less hassle.) It seems like origin could be passed to the callback passed to requestToken() along with error and user, but other things could work too.

At first I thought this would be possible using these instructions, but I couldn't get it to work. My situation is different from the one described there in that I don't use restricted() and I never send the user to a separate login page, allowing them to login via AJAX wherever they happen to be.

Implement passwordless-pouchstore

I am planning to use passwordless with my pouchdb based app, so implementing a pouchdb store for the tokens, not completely necessary but easier than installing another database.

One thing that would have been useful would be to have an implementation of an in memory passwordless store that just used plain js objects, right now I am trying to reverse engineer what mongo / redis are supposed to be doing

Allow programatic success redirects

My setup is, I am using passwordless on a single domain, 'service.pouchdb.com', the applications themselves will be hosted anywhere (typically I am hosting them on gh-pages branches on github)

My plan is when a user logs in, I send an xhr.post containing the users credentials from gh-page.com to service.com, service.com is running passwordless and sends an email that points back to service.com, on that successful login, the cookie gets set and the page redirects back to gh-page.com, CORS requests from gh-page.com are now authenticated when they hit service.com

Little complicated, but I think it should work, right now I am testing with a single app so I can hardcode the

app.use(passwordless.acceptToken({
  successRedirect: 'http://gh-pages.com/'
}));

But in future I would like to be able to redirect to wherever the request came from (probably based on a uid lookup)

passwordless + socket.io?

hello florian, is it somehow possible to use passwordless together with socket.io? I mean to validate and use the token within socket.io instead of express?

Demo doesn't work from iPad, multiple devices

First, I can't get the demo to work from my iPad. Every time I get a new token sent, it says "The supplied token is not valid (anymore). Please request another one." I've tried it three times now. I'm using email delivery and receiving the email right on the iPad.

Second, does this work by using cookies or local storage in the browser? If so, how does it work with multiple devices? Do I just need to invoke the email flow every time I want to change devices?

acceptToken false positive

I have a 2FA email scheme, and I'm currently running into a false positive. _tokenStore.authenticate is invoking the callback with valid set to false but error as null. If I were using the flash messaging, I could catch it, but since I am not I'm going through my middleware stack with an "invalid user" and no errors. I'm not sure if this is by design for 2FA.

Callback for passwordless.acceptToken()

Hi,

I have been implementing express-brute for my app that uses passwordless.

I need to add a callback to accepttoken that would just reset the counts for that specific user. I don't know how to add?

Present code :
app.use(passwordless.acceptToken({ successRedirect: '/', successFlash: 'You have successfully loggedin! ', failureFlash: 'Oops! something went wrong. Please try login again!.' }));

Code that i want to add to acceptToken() as callback :

var callback = function(req, res) { req.brute.reset(function () { res.redirect('/UI'); // logged in, send them to the home page }); }

I failed when i did this :

app.use(passwordless.acceptToken({ successRedirect: '/', successFlash: 'You have successfully loggedin! ', failureFlash: 'Oops! something went wrong. Please try login again!.' },callback));

Logout all tokens after e.g. Laptop got stolen

Imagine the following use case:
0. Passwordless is set up with new tokens invalidated after their first use. (the default)

  1. I register and login on my laptop.
  2. I don't log out.
  3. My laptop got stolen.
  4. I login on my smartphone.
  5. Now I want to logout all tokens including the one used on my laptop to prevent any abuse.

Is it possible to ask Passwordless to list all active tokens which are not logged out and log them out? (I would like to implement this on my account page with a "emergency" button.)

[Q] Getting req object in requestToken

I'd like to pass some user information with the initial login (name, etc). I figure the best place to do would be in passwordless.requestToken. That's where I've put my user lookup as a findOrCreate. It'd simplify things for me if the callback had the entire req object in instead of just (email, delivery, callback).

I'm guessing you're going to suggest having a signup step before that creates a new user and then call passwordless.requestToken. That's fine, but for my use case it'd simplify things. Any way to do that? Thanks!

troubleshooting

While not an issue with passwordless. If you think you have everything setup properly but you get a 400 error when you're redirected to /sendtoken it's probably because you forgot to set name = 'user' on your login form.

Support for hapi js ?

I know this module is written for Express. Will there be any support to make this compatible with hapi?

"static" tokens

Hi,
on my website I plan to use email tokens for authentication.
But I've would like to also send out notifications via email (something like a newsletter that is differently generated for every user). I'd like to put in these emails that go out a button that says "enter the website" and that button should open the website and log the user in.

Is it something that can be done in some way with passwordless or is it an unsupported scenario?

Thanks.

Swap out base58 dependency

Any chance you could swap out the base58-native dependency for bs58 module instead?

Appears to me that bs58 is more actively maintained, is pure js implementation and therefore more portable (doesn't require compilers, python, etc). Currently passwordless can't be npm installed on Windows without Visual Studio Express or Windows SDK, and python installed (for node-gyp). This is because of the bignum dependency in base58-native.

https://www.npmjs.org/package/bs58
https://github.com/cryptocoinjs/bs58

Also wouldn't be surprised if bs58 performs better.

Can't use successRedirect with cookie-session

Here's a combination that doesn't work:

  1. use the cookie-session
  2. use successRedirect

In _redirectWithSessionSave, a callback is passed to req.session.save(...) ... but the cookie-session save() function doesn't take a callback: the redirect (in the callback) won't be called. The req just hangs.

Here's the code:

redirect

I'm not sure what the right solution is ... maybe checking the req.session.save arity and calling it either in sync/async mode, as the case might be.

Document body express parser

Following the getting started guide, I hit

Error: req.body does not exist: did you provide a body parser middleware before calling acceptToken?
at /Users/daleharvey/src/janus/node_modules/passwordless/lib/passwordless/passwordless.js:387:10

HMAC

Hi, thanks for this sharing this idea and this package!
I was thinking to not store the tokens, but validate them using HMAC, like I use for validating the user accessToken present in the cookie.
What do you think about? Is it possible to detach the "store" part of this module?

Thanks!

csurf and passwordless

Hi,

I am a bit new to the concept of csurf and was wondering if using csurf makes sense while using passwordless. I read that jwt doesn't have to use csurf but since passwordless has sessionSupport() I have to ask about it?

Remotely log out user

I'm building a CMS around passwordless and I'm trying to figure out how to remotely logout a user. So, for example, an Admin user wants to kick a user from his/her session.

What I'm doing right now doesn't seem to work:

thisRedisStore = new RedisStore(settings.redis.port, settings.redis.ip)
passwordless.init(thisRedisStore);

....

function logOutUser(req,res,next) {
  console.log('uid',req.params.uid);
  thisRedisStore.invalidateUser(req.params.uid,function(err){
    if (err) { next(err); } else {
      res.send({ status : 'ok'});
    }
  });
}

I have the admin user in one browser window and the user to kick in a private browser window. After I execute the logOutUser function (without error, because I'm seeing the {status:"ok"} response), the kicked user is still able to go around the site as if logged in. Am I missing something?

I can kick the user by deleting the redis key that corresponds to the session, but I'm not sure how the hash is determined to delete it and I'm not sure of the side effects of just deleting redis key.

Different Email bodies depending on input

Hi,
I am creating dynamic email bodies and want to be able to specify which body to use ( from a range of templates) when the token is sent to the recipient.
For instance if delivery === 'template1' then it will send one body, etc etc for template2/3...
These templates have different fields that need filling before sending, but I won't know what the values are until just before sending.

passwordless.addDelivery('templateX', function(tokenToSend, uidToSend, recipient,
        callback) {

addDelivery doesn't seem to allow me to pass in other paramers - for instance the name of the template to use, and the parameters to populate it with.

Can you recommend a way for me doing this please?

EDIT:
I suppose the bit I want to be able to pass in are the parameters to the message object that will then be used by mandrill to send the email

        var message = {
            "html": emailText(true, tokenToSend, uidToSend),
            "text": emailText(false, tokenToSend, uidToSend),
            "subject": config.mandrill.subject,
            "from_email": config.mandrill.from,
            "from_name": config.mandrill.fromname,
            "to": [{
                "email": recipient,
                "name": "",
                "type": "to"
            }],
            "headers": {
                "Reply-To": config.mandrill.from
            },
        };

Thanks
Alex

Issues to implement in a single page app

Hi,
First, thanks for this module!
But i have an issue to implement it in a single page app. I used the passwordless.net source as a starting point.

As it s a single page app I need to send back to the client json instead of res.redirect.

My issue is about the /account/sendtoken route
Input validation part works but failureRedirect at the end of Request token and the final function(req, res) doesn't works and I don't understand why...

Here is the complete route, any idea why server doesn't send any response after input are validated?

/* POST login screen. */
router.post('/sendtoken',
    // Input validation
    function(req, res, next) {
        req.checkBody('user', 'Please provide a valid email address').isLength(1,200).isEmail();
        req.sanitize('user').toLowerCase();
        req.sanitize('user').trim();

        var errors = req.validationErrors(true);
        if (errors) {
            console.log('There have been validation errors: ' + errors);
            req.flash('validation', errors);
//          res.redirect('../account/login');
            res.send({'reload': true});
        } else {
            next();
        }
    },
    // Request token
    passwordless.requestToken(
        function(email, delivery, callback) {
            // Return user, if he exists, create new if he doesn't
            User.findUser(email, function(error, user) {
                if(error) {
                    callback(error.toString());
                } else if(user) {
                    callback(null, user.id);
                } else {
                    User.createOrUpdateUser(email, '', '', function(error, user) {
                        if(error) {
                            callback(error.toString());
                        } else {
                            callback(null, user.id);
                        }
                    });
                }
            });
        }, { failureRedirect: '/account/response',
                failureFlash: 'We had issues sending out this email... Could you try it at a later moment?',
                successFlash: 'You should have an email in your inbox in a couple of seconds...!' }),

    function(req, res) {
//      res.redirect('/');
        res.send({'redirect': '../../'});
});

router.get('/response',function(req,res) {
    res.send({'redirect': '../../'});
});

Statless mode and restriced access

Do you know why I cannot get access to restricted content when using stateless mode?
I get the token and uid from email and I am passing it using request parameters provided by REST Client and I always get the 401.

router.get('/restricted', passwordless.restricted(),
  function(req, res) {
    res.send('success');
  });

Purpose of Stateless / AngularJS Example

Hello Florian,

first of all, thank you for your great middleware.

I want to create a Rest API Server with Express und use your middleware (sms two-way authentication) for registration and authentication. I want to store the users in MongoDB after registration and tokenverification.

My first question is how to realize this scenario.

Therefore I´ve taken a look at the Stateless / AngularJS Example.

But I don´t understand the way passwordless is used in this example.

From the Dashboard you make a call to the route post.('/passwordless') passing the email address of the user. Then the requestToken-method is called and finally the delivery-mechanism logs an address to authenticate the user, like http://localhost:3000/#/authenticate?token=xyz&uid123.

In the next step I call the authentication page. Submitting the page, makes an api call to the route post('/login') passing ONLY the user id and the acceptToken method accepts the request generating a jwt-token, which has nothing to do with passwordless.

My questions are:

  1. Why does the acceptToken-Method accept requests without sending a valid passwordless token ?
  2. For what is the route post.('/passwordless') used in this example ? The only purpose seems to be to get the uid out of the sended email-Address.
  3. In general, sure that this is an appropiate example for using passwordless?

Thank you a lot in advance

Non SMS 2FA

Are Google Authenticator and/or Authy supported?

Handle exception during signing up

Hi, I have a signup form with some more fields except for email/user, I need to persist those information when mailing is successfull. Should it be handled in addDelivery() or requestToken()? I prefer to do that in addDelivery(), however req.body is not exposed? but if using requestToken() method, then how to handle mailing exception?

passwordless.addDelivery(
  function(tokenToSend, uidToSend, recipient, callback) {
    var host = 'localhost:3000';
    smtpServer.send({... }, function(err, message) { 
      if(err) {
        // do not signup      
        console.log(err);
      }
      // mailed success and proceed with signup
      callback(err);
    });
});

Prevent spamming emails of registered users?

Hi,

Great library but I have a stupid question. Is there a way to prevent spamming the email of registered user by the random attacker?

Any suggestions for writing error handling would be appreciated!

I use express 4.X.

The example application seems no to work

Hello,

Thanks for this lib. I'm trying to use it, but I can't get logged in.

So I tried to start from the example to gradually add features I want... but I can't get logged in the example too. I've just installed the example, bypassed the email to just log the url and return callback(). When I open the url, the page shows User: Not logged in... I can't figure what's happening...

Tokens surviving a process restart?

Perhaps I'm confused on this but if my node process restarts, the all the users will have to re-authenticate? That seems like a drag - is there a way to make the tokens survive a process restart?

Building a API around passwordless

Hey!

Im building a API that uses passwordless and have run in to some problems.

Following your example, I set /verify to return 200 on success. However, it always return 200, even if the tokens are invalid.

Is this because im not redirecting to a restricted route?

edit:

Solved this, do not know if this is the correct way of doing this.

app.route('/verify')
  .post(passwordless.acceptToken({failureRedirect: '/auth', failureFlash: 'Error', allowPost: true}),
  function (req, res) {
    res.redirect('/success');
  }
);
app.get('/success', passwordless.restricted({ failureRedirect: '/auth' }),
  function(req,res){
    res.send(200);
  }
);

MongoClient auth issue

Hi,

Nice work! This is exactly what i need!

I'm experiencing some auth issues I would like to share..

I'm trying to test your library and created a new sandbox database in mongolab for it.
I took your simple-mail app and replaced the initialization of the MongoStore with my connection string so it looks like this:

var connString = 'mongodb://' + config.user + ':' + config.pass + '@' + config.mongodb);
passwordless.init(new MongoStore(connString));

I launched your example, and after entering my email and pressing "login / register" it tries to post a request to '/sendToken' and crashes with Error connecting to MongoDB: MongoError: auth fails error at mongostore.js:188:11.

So... I thought something is wrong with my connection string, however, I created a simple test code and it prints 'SUCCESS':

var client = require('mongodb').MongoClient
var connString = 'mongodb://' + config.user + ':' + config.pass + '@' + config.mongodb);
client.connect(connString, {}, function(err) {
  if(err) {
    console.log('ERROR');
  } else {
    console.log('SUCCESS');
  }
})

I added a console log to the self._uri and self._options at mongostore.js:188 just to be sure i'm sending the same parameters to the MongoClient.connect and it looks the same (same connString and same options - an empty object).

Do you have any idea what may be the reason for that? Have you guys ever experienced such thing?

Better error reporting

For some reason I assumed passwordless was looking for 'req.email' as the form post data, spent a while with stuff going wrong (cors, bodyparser, this), before I just went through the code and figured it was looking for req.user

All working now, but was a snag that I can imagine happening for new users a lot

http referer header contains the uid and token

To reproduce,

  1. visit https://passwordless.net/account/login
  2. enter your email address
  3. visit your inbox and click through on the link from your confirmation email
  4. click an outbound link on the page, for example the 'fork me on github' link.
  5. check the web inspector on chrome or ff to see your uid and token contained on the referer header field.

One solution to this would be to use a fragment in the url much like the waterken system does (http://waterken.sourceforge.net/web-key/).

Use with angular single page router

Is there some trick to make this module work with angular router on a single page app? What I mean is, when using nodeJS for API and angular for front-end app, direct URL and API calls are secured, but clicking on menu links in angular app will just open the restricted page.

All I can think of is have angular router do a redirection if user data is not present, but maybe someone knows most elegant and secure way to do this.

Help with Cookie-Session Support

I need the sessions to be persistent when the application restarts, I've found this so I've migrated my project from the standard express-session to cookie-session and the integration with passwordless works fine.

There's only one problem that I've temporary patched (but I need to find a proper way to put it in production) which is:

After logging out, the session is destroyed and when I call any route that is restricted by the passwordless middleware, the next() is never called so the browser gets stuck.

Code

I have the following route

Express App

app.set('trust proxy', 1);
app.use(cookieParser());
app.use(cookieSession({
    name: 'projectName.sess',
    keys: ['key1', 'key2'],
    secret: 'secret here',
    maxAge: 1000*60*60*24*30
}));
app.use(passwordless.sessionSupport());

Logout

router.get('/logout', passwordless.logout(),
    function(req, res) {
  res.redirect('/');
});

Profile

router.get('/profile', passwordless.restricted({ failureRedirect: '/signin', failureFlash: 'Please login to view your profile.' }),
    function(req, res) {
        res.render('profile', { user: req.user });
});

The problem in /lib/passwordless/passwordless.js:605-611

        req.session.save(function(err) {
            if (err) {
                return next(err);
            } else {
                res.redirect(target);
            }
        });

Apparently using cookie-session the save function's callback (err) is not invoked and the if statement is never executed.

I've temporary fixed replacing this entire block with the following:

req.session.save();
res.redirect(target);

but I'm not 100% sure that I'm doing it right.

I hope you can help me on this :)

Trouble with restricted middleware

So everything seems to be working fine up to the point where I use restricted middleware. I step through this code and I do not have a req[self._userProperty] so it shunts off to the failure path. I also checked the session and there is nothing in there either. But if i log the session in my later handlers it is there and it has a passwordless property with my email.

var app = express();
app.use(compression({threshold: 512}));
app.use(httpLogger);
app.use(appRoot, serveStatic(staticDir));
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(cookieParser("sd;safklsad-ASDFAS@#%#$&$%#DKasdfljdsak"));
app.use(session({
    name:       "sid",
    secret:     "sdlajflksKdj234lk23jl4",
    cookie:     {httpOnly: true, secure: true, maxAge: 1000 * 60 * 30},
    store:      sessionStore,
    resave:     false,
    saveUninitialized: false
}));
app.use(flash());

// passwordless config ------------------------------------------------------------------------------
var sendgrid = require('sendgrid')("xxxxxxxx", "xxxxxxxxxxxx");
var passwordless = require('passwordless');
//var PasswordlessRedisstore = require('passwordless-redisstore');
var MemoryStore = require('passwordless-memorystore');

var tokenKey = 'token:';
var ttl = {ttl: 1000*60*10};

passwordless.init(new MemoryStore());
passwordless.addDelivery(function(tokenToSend, uidToSend, recipient, callback) {
    // Send out a token
    var body = 'Hello!\nAccess your account here: http://' + host + '?token='
                + tokenToSend + '&uid=' + encodeURIComponent(uidToSend);
    sendgrid.send({
        to:       uidToSend,
        from:     '[email protected]',
        subject:  'Login to ' + host,
        text:     body
    }, function(err, json) {
        console.log(err, json);
        callback(err, json);
    });
}, ttl);
app.use(passwordless.sessionSupport());
app.use(passwordless.acceptToken({successRedirect: '/user'}));
app.post('/sendtoken', passwordless.requestToken(function(user, delivery, callback, req) {
        callback(null, user);
    }), function(req, res) {
        res.json({token: "sent"});
    });

app.get('/user', passwordless.restricted(), function(req, res) {
    res.json({user: req.user, session: req.session, sessions: req.sessionStore.sessions});
});

Access link should be valid only once

I just tried passwordless and I was surprised that the access link sent by e-mail can be used an unlimited number of times until a logout occurs. I think this is a security problem, as the link can be exchanged and used as a plain-text password, making this login method less secure than a password-based one. Also, because of the token is sent to the server in a GET request, it is also logged in servers and proxies.
I think a better implementation of this idea would be that the access link can be clicked only once (just like a common "reset password" link). After that, the client engages a session with the server and the token is immediatley invalidated, so no one else could access that account from that link.

Error: Cannot find module 'node-uuid'

Hi,

Just cloned the repo and tried to load the simple-mail. After npm install I try to run it but I get:

Error: Cannot find module 'node-uuid'

The module is installed.

Any advice?

Thanks

Using passwordless with Express & Mongoose

So I'm using Express & Mongoose for my application and I wanted to do the authentication myself but Passwordless seems the way to go. I love tokens:)!

But is it possible to just store the tokens in my already existing database?

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.