{"id":13517474,"url":"https://github.com/florianheinemann/passwordless","last_synced_at":"2025-05-14T23:08:07.487Z","repository":{"id":16749486,"uuid":"19507083","full_name":"florianheinemann/passwordless","owner":"florianheinemann","description":"node.js/express module to authenticate users without password","archived":false,"fork":false,"pushed_at":"2019-12-19T14:38:24.000Z","size":685,"stargazers_count":1946,"open_issues_count":24,"forks_count":127,"subscribers_count":37,"default_branch":"master","last_synced_at":"2025-04-13T19:39:25.348Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/florianheinemann.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-05-06T19:09:02.000Z","updated_at":"2025-04-06T12:22:09.000Z","dependencies_parsed_at":"2022-07-14T11:47:14.392Z","dependency_job_id":null,"html_url":"https://github.com/florianheinemann/passwordless","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/florianheinemann%2Fpasswordless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/florianheinemann%2Fpasswordless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/florianheinemann%2Fpasswordless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/florianheinemann%2Fpasswordless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/florianheinemann","download_url":"https://codeload.github.com/florianheinemann/passwordless/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254243363,"owners_count":22038046,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T05:01:34.145Z","updated_at":"2025-05-14T23:08:02.473Z","avatar_url":"https://github.com/florianheinemann.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","others","📦 Legacy \u0026 Inactive Projects"],"sub_categories":[],"readme":"# Passwordless\n\nPasswordless is a modern node.js module for [Express](http://expressjs.com/) 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](https://medium.com/@ninjudd/passwords-are-obsolete-9ed56d483eb)\"\n\nToken-based authentication is...\n* **Faster to implement** compared to typical user auth systems (you only need one form)\n* **Better for your users** as they get started with your app quickly and don't have to remember passwords\n* **More secure** for your users avoiding the risks of reused passwords\n\n## Getting you started\n\nThe following should provide a quick-start in using Passwordless. If you need more details check out the [example](https://github.com/florianheinemann/passwordless/tree/master/examples/simple-mail), the [deep dive](https://github.com/florianheinemann/passwordless/blob/master/DEEPDIVE.MD), or the [documentation](https://florianheinemann.com/passwordless/Passwordless.html). Also, don't hesitate to raise comments and questions on [GitHub](https://github.com/florianheinemann/passwordless/issues).\n\n### 1. Install the module:\n\n`$ npm install passwordless --save`\n\nYou'll also want to install a [TokenStore](https://github.com/florianheinemann/passwordless/blob/master/PLUGINS.md) such as [MongoStore](https://github.com/florianheinemann/passwordless-mongostore) and something to deliver the tokens (be it email, SMS or any other means). For example:\n\n`$ npm install passwordless-mongostore --save`\n\n`$ npm install emailjs --save`\n\nIf you need to store your tokens differently consider [developing a new TokenStore](https://github.com/florianheinemann/passwordless-tokenstore-test) and [let us know](https://twitter.com/thesumofall).\n\n### 2. Require the needed modules\nYou will need:\n* Passwordless\n* A TokenStore to store the tokens such as [MongoStore](https://github.com/florianheinemann/passwordless-mongostore)\n* Something to deliver the tokens such as [emailjs](https://github.com/eleith/emailjs) for email or [twilio](https://www.twilio.com/docs/node/install) for text messages / SMS\n\n```javascript\nvar passwordless = require('passwordless');\nvar MongoStore = require('passwordless-mongostore');\nvar email   = require('emailjs');\n```\n\n### 3. Setup your delivery\nThis is very much depending on how you want to deliver your tokens, but if you use emailjs this could look like this:\n```javascript\nvar smtpServer  = email.server.connect({\n   user:    yourEmail, \n   password: yourPwd, \n   host:    yourSmtp, \n   ssl:     true\n});\n```\n\n### 4. Initialize Passwordless\n`passwordless.init()` will take your TokenStore, which will store the generated tokens as shown below for a MongoStore:\n```javascript\n// Your MongoDB TokenStore\nvar pathToMongoDb = 'mongodb://localhost/passwordless-simple-mail';\npasswordless.init(new MongoStore(pathToMongoDb));\n```\n\n### 5. Tell Passwordless how to deliver a token\n`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:\n\n`http://www.example.com/?token={TOKEN}\u0026uid={UID}`\n\nThat's how you could do this with emailjs:\n```javascript\n// Set up a delivery service\npasswordless.addDelivery(\n\tfunction(tokenToSend, uidToSend, recipient, callback, req) {\n\t\tvar host = 'localhost:3000';\n\t\tsmtpServer.send({\n\t\t\ttext:    'Hello!\\nAccess your account here: http://' \n\t\t\t+ host + '?token=' + tokenToSend + '\u0026uid=' \n\t\t\t+ encodeURIComponent(uidToSend), \n\t\t\tfrom:    yourEmail, \n\t\t\tto:      recipient,\n\t\t\tsubject: 'Token for ' + host\n\t\t}, function(err, message) { \n\t\t\tif(err) {\n\t\t\t\tconsole.log(err);\n\t\t\t}\n\t\t\tcallback(err);\n\t\t});\n});\n```\n\n### 6. Setup the middleware for express\n```javascript\napp.use(passwordless.sessionSupport());\napp.use(passwordless.acceptToken({ successRedirect: '/'}));\n```\n\n`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](https://github.com/expressjs/session) how to setup sessions if you are unsure. Please be aware: If you decide to use [cookie-session](https://github.com/expressjs/cookie-session) rather than e.g. express-session as your middleware you have to set `passwordless.init(tokenStore, {skipForceSessionSave:true})`\n\n`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.\n\nInstead of accepting tokens on any URL as done above you can also restrict the acceptance of tokens to certain URLs:\n```javascript\n// Accept tokens only on /logged_in (be sure to change the\n// URL you deliver in step 5)\nrouter.get('/logged_in', passwordless.acceptToken(), \n\tfunction(req, res) {\n\t\tres.render('homepage');\n});\n```\n\n### 7. The router\nThe following takes for granted that you've already setup your router `var router = express.Router();` as explained in the [express docs](http://expressjs.com/4x/api.html#router)\n\nYou will need at least URLs to:\n* Display a page asking for the user's email (or phone number, ...)\n* Receive these details (via POST) and identify the user\n\nFor example like this:\n```javascript\n/* GET login screen. */\nrouter.get('/login', function(req, res) {\n   res.render('login');\n});\n\n/* POST login details. */\nrouter.post('/sendtoken', \n\tpasswordless.requestToken(\n\t\t// Turn the email address into an user's ID\n\t\tfunction(user, delivery, callback, req) {\n\t\t\t// usually you would want something like:\n\t\t\tUser.find({email: user}, callback(ret) {\n\t\t\t   if(ret)\n\t\t\t      callback(null, ret.id)\n\t\t\t   else\n\t\t\t      callback(null, null)\n\t      })\n\t      // but you could also do the following \n\t      // if you want to allow anyone:\n\t      // callback(null, user);\n\t\t}),\n\tfunction(req, res) {\n\t   // success!\n  \t\tres.render('sent');\n});\n```\n\nWhat 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 user@example.com 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](https://github.com/expressjs/body-parser)).\n\nMost 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)`.\n\nIn an even simpler scenario and if you just have a fixed list of users do the following:\n```javascript\n// GET login as above\n\nvar users = [\n\t{ id: 1, email: 'marc@example.com' },\n\t{ id: 2, email: 'alice@example.com' }\n];\n\n/* POST login details. */\nrouter.post('/sendtoken', \n\tpasswordless.requestToken(\n\t\tfunction(user, delivery, callback) {\n\t\t\tfor (var i = users.length - 1; i \u003e= 0; i--) {\n\t\t\t\tif(users[i].email === user.toLowerCase()) {\n\t\t\t\t\treturn callback(null, users[i].id);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcallback(null, null);\n\t\t}),\n\t\tfunction(req, res) {\n\t\t\t// success!\n\t\tres.render('sent');\n});\n```\n\n### 8. Login page\nAll you need is a form where users enter their email address, for example:\n```html\n\u003chtml\u003e\n\t\u003cbody\u003e\n\t\t\u003ch1\u003eLogin\u003c/h1\u003e\n\t\t\u003cform action=\"/sendtoken\" method=\"POST\"\u003e\n\t\t\tEmail:\n\t\t\t\u003cbr\u003e\u003cinput name=\"user\" type=\"text\"\u003e\n\t\t\t\u003cbr\u003e\u003cinput type=\"submit\" value=\"Login\"\u003e\n\t\t\u003c/form\u003e\n\t\u003c/body\u003e\n\u003c/html\u003e\n```\nBy default, Passwordless will look for a field called `user` submitted via POST.\n\n### 9. Protect your pages\nYou can protect all pages that should only be accessed by authenticated users by using the `passwordless.restricted()` middleware, for example:\n```javascript\n/* GET restricted site. */\nrouter.get('/restricted', passwordless.restricted(),\n function(req, res) {\n  // render the secret page\n});\n```\nYou can also protect a full path, by adding:\n```javascript\nrouter.use('/admin', passwordless.restricted());\n```\n\n### 10. Who is logged in?\nPasswordless 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:\n```javascript\nrouter.get('/admin', passwordless.restricted(),\n\tfunction(req, res) {\n\t\tres.render('admin', { user: req.user });\n});\n```\nYou 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()`:\n```javascript\napp.use(function(req, res, next) {\n\tif(req.user) {\n\t\tUser.findById(req.user, function(error, user) {\n\t\t\tres.locals.user = user;\n\t\t\tnext();\n\t\t});\n\t} else { \n\t\tnext();\n\t}\n})\n```\n\n## Common options\n### Logout\nJust call `passwordless.logout()` as in:\n```javascript\nrouter.get('/logout', passwordless.logout(),\n\tfunction(req, res) {\n\t\tres.redirect('/');\n});\n```\n\n### Redirects\nRedirect non-authorised users who try to access protected resources with `failureRedirect` (default is a 401 error page):\n```javascript\nrouter.get('/restricted', \n\tpasswordless.restricted({ failureRedirect: '/login' });\n```\n\nRedirect unsuccessful login attempts with `failureRedirect` (default is a 401 or 400 error page):\n```javascript\nrouter.post('/login', \n\tpasswordless.requestToken(function(user, delivery, callback) {\n\t\t// identify user\n}, { failureRedirect: '/login' }),\n\tfunction(req, res){\n\t\t// success\n});\n```\n\nAfter the successful authentication through `acceptToken()`, you can redirect the user to a specific URL with `successRedirect`:\n```javascript\napp.use(passwordless.acceptToken(\n\t{ successRedirect: '/' }));\n```\nWhile 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.\n\n### Error flashes\nError 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](https://www.npmjs.org/package/connect-flash) installed.\n\nError flashes are supported in any middleware of Passwordless that supports `failureRedirect` (see above) but only(!) if `failureRedirect` is also supplied: \n- `restricted()` when the user is not authorized to access the resource\n- `requestToken()` when the supplied user details are unknown\n\nAs an example:\n```javascript\nrouter.post('/login', \n\tpasswordless.requestToken(function(user, delivery, callback) {\n\t\t// identify user\n}, { failureRedirect: '/login', failureFlash: 'This user is unknown!' }),\n\tfunction(req, res){\n\t\t// success\n});\n```\n\nThe error flashes are pushed onto the `passwordless` array of your flash middleware. Check out the [connect-flash docs](https://github.com/jaredhanson/connect-flash) how to pull the error messages, but a typical scenario should look like this:\n\n```javascript\nrouter.get('/mistake',\n\tfunction(req, res) {\n\t\tvar errors = req.flash('passwordless'), errHtml;\n\t\tfor (var i = errors.length - 1; i \u003e= 0; i--) {\n\t\t\terrHtml += '\u003cp\u003e' + errors[i] + '\u003c/p\u003e';\n\t\t}\n\t\tres.send(200, errHtml);\n});\n```\n\n### Success flashes\nSimilar 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](https://www.npmjs.org/package/connect-flash) installed.\n\nSuccess flashes are supported by the following middleware of Passwordless:\n- `acceptToken()` when the token was successfully validated\n- `logout()` when the user was logged in and was successfully logged out\n- `requestToken()` when the token was successfully stored and send out to the user\n\nConsider the following example:\n```javascript\nrouter.get('/logout', passwordless.logout( \n\t{successFlash: 'Hope to see you soon!'} ),\n\tfunction(req, res) {\n  \tres.redirect('/home');\n});\n```\n\nThe messages are pushed onto the `passwordless-success` array of your flash middleware. Check out the [connect-flash docs](https://github.com/jaredhanson/connect-flash) how to pull the messages, but a typical scenario should look like this:\n\n```javascript\nrouter.get('/home',\n\tfunction(req, res) {\n\t\tvar successes = req.flash('passwordless-success'), html;\n\t\tfor (var i = successes.length - 1; i \u003e= 0; i--) {\n\t\t\thtml += '\u003cp\u003e' + successes[i] + '\u003c/p\u003e';\n\t\t}\n\t\tres.send(200, html);\n});\n```\n\n### 2-step authentication (e.g. for SMS)\nFor 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. \n\nTo achieve this, requestToken stores the requested UID in `req.passwordless.uidToAuth`. Putting it all together, take the following steps:\n\n**1: Read out `req.passwordless.uidToAuth`**\n\n```javascript\n// Display a new form after the user has submitted the phone number\nrouter.post('/sendtoken', passwordless.requestToken(function(...) { },\n\tfunction(req, res) {\n  \tres.render('secondstep', { uid: req.passwordless.uidToAuth });\n});\n```\n\n**2: Display another form to submit the token submitting the UID in a hidden input**\n\n```html\n\u003chtml\u003e\n\t\u003cbody\u003e\n\t\t\u003ch1\u003eLogin\u003c/h1\u003e\n\t\t\u003cp\u003eYou should have received a token via SMS. Type it in below:\u003c/p\u003e\n\t\t\u003cform action=\"/auth\" method=\"POST\"\u003e\n\t\t\tToken:\n\t\t\t\u003cbr\u003e\u003cinput name=\"token\" type=\"text\"\u003e\n\t\t\t\u003cinput type=\"hidden\" name=\"uid\" value=\"\u003c%= uid %\u003e\"\u003e\n\t\t\t\u003cbr\u003e\u003cinput type=\"submit\" value=\"Login\"\u003e\n\t\t\u003c/form\u003e\n\t\u003c/body\u003e\n\u003c/html\u003e\n```\n\n**3: Allow POST to accept tokens**\n\n```javascript\nrouter.post('/auth', passwordless.acceptToken({ allowPost: true }),\n\tfunction(req, res) {\n\t\t// success!\n});\n```\n\n### Successful login and redirect to origin\nPasswordless 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:\n\n**1: Set `originField` and `failureRedirect` for passwordless.restricted()**\n\nDoing this will call `/login` with `/login?origin=/admin` to allow later reuse\n```javascript\nrouter.get('/admin', passwordless.restricted( \n\t{ originField: 'origin', failureRedirect: '/login' }));\n```\n\n**2: Display `origin` as hidden field on the login page**\n\nBe sure to pass `origin` to the page renderer.\n```html\n\u003cform action=\"/sendtoken\" method=\"POST\"\u003e\n\tToken:\n\t\u003cbr\u003e\u003cinput name=\"token\" type=\"text\"\u003e\n\t\u003cinput type=\"hidden\" name=\"origin\" value=\"\u003c%= origin %\u003e\"\u003e\n\t\u003cbr\u003e\u003cinput type=\"submit\" value=\"Login\"\u003e\n\u003c/form\u003e\n```\n\n**3: Let `requestToken()` accept `origin`**\n\nThis will store the original URL next to the token in the TokenStore.\n```javascript\napp.post('/sendtoken', passwordless.requestToken(function(...) { }, \n\t{ originField: 'origin' }),\n\tfunction(req, res){\n\t\t// successfully sent\n});\n```\n\n**4: Reconfigure `acceptToken()` middleware**\n\n```javascript\napp.use(passwordless.acceptToken( { enableOriginRedirect: true } ));\n```\n\n### Several delivery strategies\nIn case you want to use several ways to send out tokens you have to add several delivery strategies to Passwordless as shown below:\n```javascript\npasswordless.addDelivery('email', \n\tfunction(tokenToSend, uidToSend, recipient, callback) {\n\t\t// send the token to recipient\n});\npasswordless.addDelivery('sms', \n\tfunction(tokenToSend, uidToSend, recipient, callback) {\n\t\t// send the token to recipient\n});\n```\nTo 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:\n```javascript\nrouter.post('/sendtoken', \n\tpasswordless.requestToken(\n\t\tfunction(user, delivery, callback) {\n\t\t\tif(delivery === 'sms')\n\t\t\t\t// lookup phone number\n\t\t\telse if(delivery === 'email')\n\t\t\t\t// lookup email\n\t\t}),\n\tfunction(req, res) {\n  \t\tres.render('sent');\n});\n```\n\n### Modify lifetime of a token\nThis is particularly useful if you use shorter tokens than the default to keep security on a high level:\n```javascript\n// Lifetime in ms for the specific delivery strategy\npasswordless.addDelivery(\n\tfunction(tokenToSend, uidToSend, recipient, callback) {\n\t\t// send the token to recipient\n}, { ttl: 1000*60*10 });\n```\n\n### Allow token reuse\nBy 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.\n\nTo enable the reuse of tokens call `init()` with the option `allowTokenReuse: true`, as shown here:\n```javascript\npasswordless.init(new TokenStore(), \n\t{ allowTokenReuse: true });\n```\n\n### Different tokens\nYou 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):\n```javascript\npasswordless.addDelivery(\n\tfunction(tokenToSend, uidToSend, recipient, callback) {\n\t\t// send the token to recipient\n}, {tokenAlgorithm: function() {return 'random'}});\n```\n\n### Stateless operation\nJust 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:\n* 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)\n* Tokens have a limited lifetime. Consider extending it (for details see above), but be aware about the involved security risks\n* Consider switching off redirects such as `successRedirect` on the `acceptToken()` middleware\n\n## The tokens and security\nBy 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](https://github.com/florianheinemann/passwordless-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.\n\n## Further documentation\n- [Full API documentation](https://florianheinemann.com/passwordless/Passwordless.html)\n- [Deep dive](https://github.com/florianheinemann/passwordless/blob/master/DEEPDIVE.MD)\n\n## Tests\nDownload the whole repository and call:\n`$ npm test`\n\n## License\n\n[MIT License](http://opensource.org/licenses/MIT)\n\n## Author\nFlorian Heinemann [@thesumofall](http://twitter.com/thesumofall/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflorianheinemann%2Fpasswordless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflorianheinemann%2Fpasswordless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflorianheinemann%2Fpasswordless/lists"}