{"id":21261505,"url":"https://github.com/chenasraf/express-otp","last_synced_at":"2026-05-03T10:31:25.818Z","repository":{"id":63770147,"uuid":"570595006","full_name":"chenasraf/express-otp","owner":"chenasraf","description":"OTP auth for your nodejs/express app, as easy as it gets!","archived":false,"fork":false,"pushed_at":"2022-12-04T23:21:39.000Z","size":614,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"develop","last_synced_at":"2025-01-21T22:11:23.647Z","etag":null,"topics":["auth","authentication","express","middleware","nodejs","otp","typescript"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/express-otp","language":"TypeScript","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/chenasraf.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"chenasraf","patreon":null,"open_collective":null,"ko_fi":"casraf","tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2022-11-25T15:12:46.000Z","updated_at":"2024-12-05T17:30:35.000Z","dependencies_parsed_at":"2023-01-23T00:46:05.970Z","dependency_job_id":null,"html_url":"https://github.com/chenasraf/express-otp","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenasraf%2Fexpress-otp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenasraf%2Fexpress-otp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenasraf%2Fexpress-otp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chenasraf%2Fexpress-otp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chenasraf","download_url":"https://codeload.github.com/chenasraf/express-otp/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243695589,"owners_count":20332629,"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":["auth","authentication","express","middleware","nodejs","otp","typescript"],"created_at":"2024-11-21T04:43:36.404Z","updated_at":"2026-05-03T10:31:20.797Z","avatar_url":"https://github.com/chenasraf.png","language":"TypeScript","funding_links":["https://github.com/sponsors/chenasraf","https://ko-fi.com/casraf","https://ko-fi.com/casraf'"],"categories":[],"sub_categories":[],"readme":"# express-otp\n\n\u003ch2 align=\"center\"\u003e\n\n[GitHub](https://github.com/chenasraf/express-otp) | [Documentation](https://casraf.dev/express-otp)\n| [NPM](https://npmjs.com/package/express-otp) | [casraf.dev](https://casraf.dev)\n\n\u003c/h2\u003e\n\n![npm-version](https://img.shields.io/npm/v/express-otp)\n![gh-build](https://img.shields.io/github/workflow/status/chenasraf/express-otp/Release/master)\n\nIncredibly simple TOTP authentication for your Node.JS/Express backend.\n\nUse it for authenticating either select admins to perform sensitive actions, or use it as the main\nauth mechanism for your app.\n\nSet-up is incredibly easy!\n\n## Getting started\n\nSee [src/example/server.ts](src/example/server.ts) for a fully working example usage.\n\n### Initialize\n\nFirst, use the `otp` function to generate middleware and functions for your use:\n\n```typescript\nimport otp from 'express-otp'\n\nconst totp = otp({\n  // Any identifier that is for your app\n  issuer: 'my-issuer',\n\n  // This should return user information if a request contains a valid user\n  // attempt (such as username or email)\n  getUser: async (req) =\u003e {\n    const user = await db.users.findOne({ username: req.query.username })\n    if (!user) {\n      return undefined\n    }\n    return { user: user.details, secret: user.secret, username: user.username }\n  },\n\n  // By default, the token is fetched using `req.query.token`. You can change\n  // that by providing a `getToken` option:\n  getToken: (req) =\u003e req.headers['X-OTP-Token'] as string,\n\n  // Use this option to immediately respond with an error when a token is\n  // missing/invalid. If this is omitted, the next route/middleware will fire\n  // normally, but without `req.user` injected. Providing this function ends\n  // the response if it's fired.\n  errorResponse(req, res, next, error) {\n    res.send(error.message)\n    res.status(401)\n  },\n\n  // Use `true` for default OTP form, or respond with your own form in a function.\n  // The default form will redirect to the same URL with the `token` URL\n  // parameter added. To modify this behavior to use another token method,\n  // you will have to supply your own form.\n  tokenForm: true,\n  tokenForm(req, res) {\n    res.render(myFormPage)\n  },\n})\n```\n\n### Generate a user secret\n\nTokens must be valid 32-bit strings (characters `A-Z` and `2-7` and `=`).\n\nYou can supply your own tokens, or generate one using `generateNewSecret()`.\n\n```typescript\nconst token = totp.generateNewSecret()\nconsole.log(token) // example: 4XLM2M7UTOLK6JYUX7BR2KB5USM7HM6J\n```\n\n#### Generate a URL/QR\n\nYou can generate an OATH URI or a QR code containing that URI, for your users to scan and be able to\nuse in their authenticator apps.\n\n```typescript\n// generate URL\nconst uri = totp.generateSecretURL(username, secret)\n\n// generate QR data URL (for use in `\u003cimg src=\"...\"\u003e`)\nconst qrDataURL = await totp.generateSecretQR(username, secret)\n\n// write QR image directly to file - add 3rd argument\nawait totp.generateSecretQR(username, secret, '/path/to/qr.png')\n```\n\n### Authenticate a user\n\nTo lock any endpoint behind authentication, use the provided `authenticate()` middleware. If the\nuser provided the token by your specified method, the user is injected into the request.\n\nIf you specified `tokenForm` as true or your own function, a missing token will trigger that form to\nbe responded to the user.\n\nOtherwise, an error will be chained to the next middleware. You can make it respond immediately with\nan error by using `errorResponse` option.\n\nYou can pass options to the `authenticate()` method to override options from the top-level call, for\nexample setting a custom error response, or token form page.\n\nFurther requests will still need to be validated with a correct token. The authentication state will\n**not be saved in memory** between sessions - that is up to you to implement (if necessary).\n\n```typescript\napp.get('/user/me', totp.authenticate(), (req, res) =\u003e {\n  if (!req.user) {\n    res.status(401).json('Unauthorized')\n    return\n  }\n\n  res.status(200).json(req.user)\n})\n```\n\n#### Manual authentication\n\nIf you want to manually check the OTP in your own middleware, you can use the `verifyUser` and\n`verifyToken` methods. You will need to inject the user yourself in that case. However, you would\nget more fine-tuned control over the response timing \u0026 structure.\n\n```typescript\nif ('token' in req.query) {\n  console.log('Token is valid:', totp.verifyToken(userSecret, req.token))\n  const user = await totp.verifyUser(req)\n  if (!user) {\n    next(new Error('Invalid OTP token'))\n    return\n  }\n  req.user = user\n  next(null)\n  return\n}\n```\n\n### Customizing error/token form\n\nYou can supply functions that will act as middleware when getting a request with no token, or an\ninvalid token or a missing user.\n\nWhen either of theses are specified, they are used instead of the default behaviors.\n\n```typescript\nconst totp = otp({\n  // custom token form\n  tokenForm(req, res) {\n    res.status(200).render(myTokenForm)\n  },\n  // custom error page\n  errorResponse(req, res, next, error) {\n    res.status(400).json({\n      code: error.type,\n      message: error.message,\n    })\n  },\n})\n```\n\n## Contributing\n\nI am developing this package on my free time, so any support, whether code, issues, or just stars is\nvery helpful to sustaining its life. If you are feeling incredibly generous and would like to donate\njust a small amount to help sustain this project, I would be very very thankful!\n\n\u003ca href='https://ko-fi.com/casraf' target='_blank'\u003e\n  \u003cimg height='36' style='border:0px;height:36px;'\n    src='https://cdn.ko-fi.com/cdn/kofi1.png?v=3'\n    alt='Buy Me a Coffee at ko-fi.com' /\u003e\n\u003c/a\u003e\n\nI welcome any issues or pull requests on GitHub. If you find a bug, or would like a new feature,\ndon't hesitate to open an appropriate issue and I will do my best to reply promptly.\n\nIf you are a developer and want to contribute code, here are some starting tips:\n\n1. Fork this repository\n2. Run `yarn install`\n3. Run `yarn start` to start file watch mode\n4. Make any changes you would like\n5. Create tests for your changes\n6. Update the relevant documentation (readme, code comments, type comments)\n7. Create a PR on upstream\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchenasraf%2Fexpress-otp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchenasraf%2Fexpress-otp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchenasraf%2Fexpress-otp/lists"}