【问题标题】:Next.js Authentication StrategiesNext.js 身份验证策略
【发布时间】:2020-07-03 02:24:47
【问题描述】:

我一直在尝试为 Next.js 项目实施可靠的身份验证流程,但我现在完全迷失了。我已经看过 Next.js 的示例 repo。但是对于完整的解决方案,我有很多问题。

我有一个 express.js API 和一个单独的 Next.js 前端项目。所有数据和身份验证均由 API 处理。前端只是用 SSR 渲染页面。如果我只是创建一个单体项目,其中呈现页面和所有数据由单个服务器处理(我的意思是 Next.js 使用自定义服务器选项),我只会使用 express-session 和 csurf。这将是管理会话和针对 CSRF 创建安全性的传统方式。

Express.js API 不是必需的。这只是一个例子。它可以是 Django API 或 .Net Core API。重点是,它是一个单独的服务器和一个单独的项目。

我怎样才能拥有一个简单但可靠的结构?我检查了一些我最喜欢的网站(netlify、zeit.co、heroku、spectrum.chat 等)。其中一些使用本地存储来存储访问和刷新令牌(XSS 易受攻击)。其中一些使用 cookie,甚至不是 HTTPOnly(XSS 和 CSRF 都易受攻击)。而像spectrum.chat这样的例子使用我上面提到的方式(cookie-session +防止csrf)。

我知道围绕 JWT 代币进行了巨大的炒作。但我觉得它们太复杂了。大多数教程只是跳过了所有的过期、令牌刷新、令牌撤销、黑名单、白名单等。

Next.js 的许多会话 cookie 示例几乎从未提及 CSRF。老实说,身份验证对我来说一直是个大问题。一天我读到应该使用 HTTPOnly cookie,第二天我看到一个巨大的流行网站甚至没有使用它们。或者他们说“永远不要将你的代币存储到 localStorage”,然后一些大型项目就使用这种方法。

谁能告诉我这种情况的方向?

【问题讨论】:

  • JWT 实际上相当简单。如果你愿意,我可以发送我的例子吗?
  • 这可能会有所帮助。 JWT 可以用于很多事情,但是当人们只是创建一个令牌、在 x 小时内过期并且甚至不显示他们如何刷新它等等时,我就是不明白。

标签: reactjs authentication session csrf next.js


【解决方案1】:

免责声明:我是以下免费开源软件包的维护者,但我认为这里是合适的,因为这是一个常见问题,没有很好的答案,因为许多流行的解决方案都具有特定的安全性问题中提出的缺陷(例如未在适当的情况下使用 CSRF 并将会话令牌或 Web 令牌暴露给客户端 JavaScript)。

NextAuth.js 包试图通过免费的开源软件解决上述问题。

  • 它使用httpOnly cookie 和secure。
  • 它具有 CSRF 保护(双重提交 cookie 方法,带有签名的 cookie)。
  • Cookie 有适当的前缀(例如 __HOST- 或 __Secure)。
  • 它支持电子邮件/无密码登录和 OAuth 提供程序(包括许多)。
  • 它支持 JSON Web 令牌(签名 + 加密)和会话数据库。
  • 您可以在没有数据库的情况下使用它(例如任何 ANSI SQL、MongoDB)。
  • 有一个live demo (view source)。
  • 它是 100% FOSS,不是商业软件或 SaaS 解决方案(不销售任何东西)。

示例 API 路由

例如page/api/auth/[...nextauth.js]

import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'

const options = {
  providers: [
    // OAuth authentication providers
    Providers.Apple({
      clientId: process.env.APPLE_ID,
      clientSecret: process.env.APPLE_SECRET
    }),
    Providers.Google({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET
    }),
    // Sign in with email (passwordless)
    Providers.Email({
      server: process.env.MAIL_SERVER,
      from: '<no-reply@example.com>'
    }),
  ],
  // MySQL, Postgres or MongoDB database (or leave empty)
  database: process.env.DATABASE_URL
}

export default (req, res) => NextAuth(req, res, options)

示例反应组件

例如pages/index.js

import React from 'react'
import { 
  useSession, 
  signin, 
  signout 
} from 'next-auth/client'

export default () => {
  const [ session, loading ] = useSession()

  return <p>
    {!session && <>
      Not signed in <br/>
      <button onClick={signin}>Sign in</button>
    </>}
    {session && <>
      Signed in as {session.user.email} <br/>
      <button onClick={signout}>Sign out</button>
    </>}
  </p>
}

即使您不选择使用它,您也可能会发现代码作为参考很有用(例如如何处理 JSON Web Tokens 和 how they are rotated in sessions。

【讨论】:

  • 你有什么指导吗,我在前端使用带有 NextJs 的 Django Rest Framework。
  • 我最近一直在检查 next-auth。有没有办法使用它来注册用户并将它们存储到 mongodb 并再次从 mongodb 获取用户数据以登录?
  • 如果您提到 nextjs-auth 如何处理外部 API 身份验证(或者我们应该如何处理?),这可能是更好的答案。大多数项目都是 nextjs + 一些外部的 rest API。这是问题的重点,而您只是错过了它。
  • @ShinebayarG 如果您尝试自己回答,那将是一个更好的评论。
【解决方案2】:

我终于找到了解决办法!

现在我使用的是 csrf npm 包,而不是 csurf。 csurf 只是将 csrf 变成了一个 express 中间件。

所以,我在 _app 的 getInitialProps 中创建了一个 csrfSecret。它创建秘密,将其设置为 httpOnly cookie。稍后,它会创建一个 csrfToken 并使用 pageProps 返回它。所以,我可以通过 window.NEXT_DATA.props.csrfToken 访问它。如果用户刷新页面,csrfSecret 保持不变,但 csrfToken 会被更新。

当我向代理的“/api/graphql API 路由发出请求时,它首先从 x-xsrf-token 标头获取 csrf 令牌,并使用 csrfSecret cookie 值对其进行验证。 之后,它会提取 authToken cookie 的值并将其传递给实际的 GraphQL API。

API 都是基于令牌的。它只需要一个不会过期的访问令牌。 (顺便说一句,它不需要是 JWT。可以使用任何加密强度高的随机令牌。这意味着引用/不透明令牌。)

实际 API 不需要 CSRF 检查,因为它不依赖 cookie 进行身份验证。它只检查授权标头。 authToken 和 csrfSecret 都是 httpOnly cookie。而且我什至从不将它们存储在客户端内存中。

我认为这是尽可能安全的。现在我对这个解决方案很满意。

【讨论】:

  • 感谢您的详细回答,帮助您了解更多
【解决方案3】:

对于我当前的项目,我也不得不考虑这一点。我使用相同的技术:ExpressJS API 和 NextJS 服务器端渲染前端。

我选择在 ExpressJS API 中使用 passport.js。 YouTube 上的 TheNetNin​​ja 有一个非常好的播放列表,共有 21 集。他向您展示了如何在您的 API 中实现 Google OAuth 2.0,但此逻辑转移到任何其他策略(JWT、电子邮件 + 密码、Facebook 身份验证等)。

在前端,我会直接将用户重定向到 Express API 中的 url。该 url 将向用户显示 Google OAuth 屏幕,用户单击“允许”,API 执行更多操作,为特定用户制作 cookie,然后重定向回前端的 url。现在,用户已通过身份验证。

关于 HTTPOnly cookie:我选择关闭此功能,因为我将信息存储在前端所需的 cookie 中。如果您启用了此功能,那么前端 (javascript) 将无法访问这些 cookie,因为它们是 HTTPOnly。

这是我正在谈论的播放列表的链接: https://www.youtube.com/watch?v=sakQbeRjgwg&list=PL4cUxeGkcC9jdm7QX143aMLAqyM-jTZ2x

希望我给了你一个可以采取的方向。

编辑: 我没有回答你关于 CSURF 的问题,但那是因为我不熟悉它。

【讨论】:

  • 感谢您的建议。我一定会找那个系列的!我的意思是设置 cookie 和不在 localStorage 中存储身份验证状态的全部目的是防止 XSS。如果没有 HTTPOnly,我不明白人们是如何阻止它的。但就像我说的,zeit.co 也使用类似的东西。您是否使用反向代理或某些子域来使用您在前端 API 中设置的 cookie?
  • 顺便说一句,我的结构几乎与您描述的相同。前端重定向到后端。它处理 Google 的身份验证并设置一些 cookie。我的痛点基本上是 CSRF。另一种方法是从前端重定向到 Google 身份验证屏幕。从 Google 重定向到前端时获取授权码。将代码传递给您自己的 API,通过将代码从您的 API 发送到 Google 来验证它,然后启动会话。这是我在 Google Dev Docs 中读到的内容。我认为这与passport 所做的几乎相同。
  • 我使用的唯一子域是我的 api 的子域。这就是所有身份验证发生的地方。我也考虑过采用前端方法。在前端获取代码,然后在后端验证它,但选择不走那条路线,因为我不想让前端过于复杂。在某个时间点,我想将我的所有身份验证移动到一个专用的子域,甚至可能是一个专用的服务器。很抱歉,我无法在 CSRF 方面为您提供帮助,我将不得不阅读它,但我设法通过不在 cookie 中存储敏感信息来确保安全。
  • 谢谢!我可能会深入研究这种身份验证的东西。但它只是变得越来越复杂,只是锁定了我想要实现的所有其他功能。将尝试找到一个简单但安全(至少在某种程度上)的路径。再次感谢。
  • @JonathanvandeGroep 呵呵。我也一直在努力解决这个问题。我将 nextjs 用于个人博客,但需要弄清楚管理端点的身份验证。所以你说的是我可以用我的外部 API 进行身份验证(因为我所有需要身份验证的端点也在那个 API 上)?换句话说,如果我的外部 API 处理资源创建,并且我需要对其进行 POST 身份验证,我可以直接使用 API 进行身份验证,因为两个端点共享该来源(而不是由 nextjs 服务器处理)。跨度>
猜你喜欢
  • 2019-06-12
  • 2017-05-20
  • 2019-03-23
  • 1970-01-01
  • 2021-10-09
  • 2013-07-24
  • 2018-12-01
  • 2019-12-14
  • 2019-04-30
相关资源
最近更新 更多