Next.js 身份验证策略

Next.js Authentication Strategies

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

我有一个 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 cookies,第二天我看到一个巨大的流行网站甚至没有使用它们。或者他们说 "never store your tokens to localStorage",并且一些大型项目正在使用这种方法。

任何人都可以为我指明方向吗?

对于我当前的项目,我也不得不考虑这个问题。我使用相同的技术:ExpressJS API 和 NextJS 服务器端呈现的前端。

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

在前端,我会直接将用户重定向到 Express API 中的 url。 url 会向用户显示 Google OAuth 屏幕,用户点击 "Allow",API 会做一些更多的事情,为特定用户制作 cookie,然后重定向回到前端的 url 。现在,用户已通过身份验证。

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

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

希望我已经为您指明了方向。

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

我终于找到了解决办法!

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

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

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

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

实际 API 不需要 CSRF 检查,因为它不依赖 cookie 进行身份验证。它只检查授权 header。 authToken 和 csrfSecret 都是 httpOnly cookies。我什至从未将它们存储在 client-side 内存中。

我认为这是我所能得到的最安全的。现在我对这个解决方案很满意。

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

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

  • 它使用 httpOnly 个 cookie 和 secure
  • 它具有 CSRF 保护(双重提交 cookie 方法,带有签名 cookie)。
  • Cookies 有适当的前缀(例如 __HOST-__Secure)。
  • 它支持 email/passwordless 登录和 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)

示例 React 组件

例如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 are handled and how they are rotated in sessions.