Next.js 身份验证策略

Posted

技术标签:

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

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

我有一个 express.js API 和一个单独的 Next.js 前端项目。所有数据和身份验证均由 API 处理。前端只是用 s-s-r 渲染页面。如果我只是创建一个单体项目,其中呈现页面和所有数据由单个服务器处理(我的意思是 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 小时内过期并且甚至不显示他们如何刷新它等等时,我就是不明白。 【参考方案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 服务器处理)。跨度>

以上是关于Next.js 身份验证策略的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Next.js 中实现身份验证

在 Next.js 中创建一个 HOC(高阶组件)进行身份验证

在 Next.js 中创建一个 HOC(高阶组件)进行身份验证

使用 Next-Auth 进行身份验证时如何修复内部服务器错误

Next.js 示例 Auth - 基于 auth 的重新路由,环绕其他功能

得到“错误”:“未知的身份验证策略\”jwt\“”