如何安全地通过无密码魔法链接验证用户?

huangapple go评论60阅读模式
英文:

How to securly verify user by passwordless magic link?

问题

问题:

我正在创建一个简单的免密码身份验证系统,以便用户执行基本的CRUD操作。

方法:

整个登录流程如下:

  1. 用户访问魔术链接页面。
  2. 如果电子邮件有效,将发送验证链接到用户的邮箱。
  3. 用户验证链接。
  4. 如果链接未过期且有效,则将其存储为Cookie。
  5. 利用Cookie中的信息,我可以为仅对访客可见的组件创建Guest HOC,例如登录,以及为仪表板等组件创建User HOC。

我想知道验证用户而无需密码的方法是否足够安全。

我使用的技术栈:

我使用fastify、prisma、typescript和zod进行模式验证。

我的代码目前如下:

export const app = fastify()

declare module 'fastify' {
   interface FastifyRequest {
      user: User
   }
}

app.register(fastifyCookie, {
   secret: process.env.COOKIE_SECRET,
   hook: 'onRequest',
   parseOptions: {},
})

app.setErrorHandler(function (error, _request, reply) {
   // ... 错误处理逻辑
})

const authTokenSchema = z.object({ email: z.string().email() })

const auth = async (request: FastifyRequest, reply: FastifyReply) => {
   try {
      const { authToken } = request.cookies

      // ... 身份验证逻辑

   } catch (error) {
      reply.status(500).send({ error })
   }
}

app.post('/api/login', async (req, reply) => {
   // ... 登录逻辑
})

app.get('/api/verify', async (req, reply) => {
   // ... 验证逻辑
})

app.get('/auth', { preHandler: [auth] }, async (_req, reply) => {
   // ... 鉴权逻辑
})

const port = parseInt(process.env.PORT)

app.listen({ port }, (error, address) => {
   // ... 服务器监听逻辑
})

如果有人愿意提出问题:

  1. 在验证用户而无需密码的情况下,如何确保身份验证过程的安全性?
  2. 在使用Fastify、Prisma和TypeScript实现免密码身份验证系统的代码中是否存在潜在的漏洞或安全风险?
  3. 处理和存储身份验证令牌在免密码身份验证系统中的最佳实践是什么?
  4. 是否有任何改进或修改建议,以增强使用Cookie和魔术链接的免密码身份验证方法的安全性?

谢谢!

英文:

Problem:

I am in the process of creating a straightforward passwordless authentication system for users to perform basic CRUD operations.

Approach

The overall login process is as follows:

  1. The user visits the magic link page
  2. If the email is valid, a verification link is sent to the user's
    mailbox.
  3. The user verifies the link.
  4. If the link has not expired and is valid, it is stored as a cookie.
  5. With the information from the cookie, I can make Guest HOC for components only visible for guests like login and User HOC for components like dashboard.

I would like to know if this approach to verifying users without passwords is secure enough.

Stack i use

I use fastify with prisma,typescript and zod for schema validation

My code so far

export const app = fastify()

declare module 'fastify' {
   interface FastifyRequest {
      user: User
   }
}

app.register(fastifyCookie, {
   secret: process.env.COOKIE_SECRET,
   hook: 'onRequest',
   parseOptions: {},
})

app.setErrorHandler(function (error, _request, reply) {
   if (error instanceof NotFoundError) {
      reply.status(404).send(error.message)
   }

   if (error instanceof InvalidInputError) {
      reply.status(422).send(error.issues)
   }

   if (error instanceof TokenExpiredError) {
      reply.status(403).send({ error: 'Token expired' })
   }

   if (error instanceof JsonWebTokenError) {
      reply.status(404).send({ error: 'Invalid token' })

      // Token required for the request is invalid
   }

   if (process.env.NODE_ENV == 'development') {
      console.log(error.message)
   }

   reply.status(500).send('Internal server error')
})


const authTokenSchema = z.object({ email: z.string().email() })

const auth = async (request: FastifyRequest, reply: FastifyReply) => {
   try {
      const { authToken } = request.cookies

      if (!authToken) {
         throw new NotFoundError('Token')
      }

      const decodedToken = verify(authToken, process.env.JWT_SECRET)

      const { email } = authTokenSchema.parse(decodedToken)

      const user = await prisma.user.findFirst({
         where: {
            email,
            authToken,
         },
      })

      if (!user) {
         throw new NotFoundError('User')
      }

      request.user = user
   } catch (error) {
      reply.status(500).send({ error })
   }
}

app.post('/api/login', async (req, reply) => {
   const { email } = req.body as {
      email: string
   }

   try {
      const authToken = sign({ email }, process.env.JWT_SECRET, { expiresIn: '30m' })

      const isEmailFound = await prisma.user.findFirst({ where: { email } })

      if (isEmailFound) {
         await prisma.user.update({
            where: { email },
            data: { authToken },
         })
      } else {
         await prisma.user.create({
            data: {
               email,
               authToken,
            },
         })
      }

      await emailService.sendMagicLink(email, authToken)
      reply.status(200).send({
         success: true,
         email,
      })
   } catch (error) {
      reply.status(500).send({ error })
   }
})



app.get('/api/verify', async (req, reply) => {
   const { authToken } = req.query as { authToken: string }

   // TODO: parse schemą tokena 
   const data = verify(authToken, process.env.JWT_SECRET) as { email: string }
   console.log(authToken)
   const { email } = authTokenSchema.parse(data)

   reply
      .setCookie('authToken', authToken, {
         httpOnly: true,
         secure: process.env.NODE_ENV === 'production', // true on production
      })
      .status(200)
      .send({
         success: true,
         email,
      })
})

app.get('/auth', { preHandler: [auth] }, async (_req, reply) => {
   try {
      reply.status(200).send({ success: true })
   } catch (error) {
      reply.status(403).send({ error })
   }
})

const port = parseInt(process.env.PORT)

app.listen({ port }, (error, address) => {
   if (error) logger.error(error)

   logger.info(`Server listening on on ${address}`)
})

If someone is kind enught a have some questions

  1. How can I ensure the security of the authentication process in my
    passwordless approach when verifying users without passwords?
  2. Are there any potential vulnerabilities or security risks in the
    code implementation of my passwordless authentication system using
    Fastify, Prisma, and TypeScript?
  3. What are the best practices for handling and storing authentication
    tokens in a passwordless authentication system?
  4. Are there any improvements or modifications I should consider to
    enhance the security of my passwordless authentication approach
    using cookies and magic links?

Thanks

答案1

得分: 1

> I would like to know if this approach to verifying users without passwords is secure enough.
"我想知道在验证用户无需密码的方法是否足够安全。"

> 1. How can I ensure the security of the authentication process in my passwordless approach when verifying users without passwords?
"在验证用户无需密码的情况下,如何确保我的无密码验证方法的安全性?"

> 2. Are there any potential vulnerabilities or security risks in the code implementation of my passwordless authentication system using Fastify, Prisma, and TypeScript?
"在使用Fastify、Prisma和TypeScript实现的无密码验证系统的代码实现中是否存在潜在的漏洞或安全风险?"

> 3. What are the best practices for handling and storing authentication tokens in a passwordless authentication system?
"在无密码验证系统中处理和存储身份验证令牌的最佳实践是什么?"

英文:

> I would like to know if this approach to verifying users without passwords is secure enough.

Secure enough for what? This isn't a yes/no answer. Everything has tradeoffs.

In this case, it's only secure as the e-mail, and anyone who the user inadvertently sends it to. You have to trust all possible recipients of that message, the user, the user's browser and its extensions, etc. If you're fine with that, then it's secure enough. If not, well, then, not.

> 1. How can I ensure the security of the authentication process in my passwordless approach when verifying users without passwords?

Testing, auditing, third-party review, etc.

Have you also considered checking out the common attacks for JWT on OWASP?

> 2. Are there any potential vulnerabilities or security risks in the code implementation of my passwordless authentication system using Fastify, Prisma, and TypeScript?

The answer to this question is always yes... there are always potential vulnerabilities or security risks. It's up to you to understand them and decide what risks you will take for the convenience of your users.

As far as specific issues go... you haven't shown us code in main functions like verify() so who knows. I am curious though... it appears that you're not just allowing token re-use but requiring it for your system to work?

> 3. What are the best practices for handling and storing authentication tokens in a passwordless authentication system?

I'm not sure why you're storing the tokens to begin with. If you include the relevant information, like a session ID, and sign it, then the server can statelessly determine if the session ID was properly created. You can also encrypt the token to hide data within it that the user's browser tracks for you, but only the API server can decrypt. Either method doesn't require storing the token.

huangapple
  • 本文由 发表于 2023年7月3日 03:17:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/76600446.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定