如何向 Nextjs 节点服务器添加基本身份验证?

OZZ*_*ZIE 7 node.js next.js

不想向某些外部请求或 OAuth 添加身份验证。只需对节点服务器进行服务器端基本身份验证。

截至编写 Next.js 似乎不支持它:https ://github.com/vercel/next.js/discussions/17719

它支持身份验证,但我想它必须添加到应用程序的每个路由中: https: //nextjs.org/docs/authentication

Su-*_*ang 11

--- 更新为 NextJS 12.2 ---

Nextjs 的当前版本允许您通过在目录(pages 文件夹旁边)中添加一个middleware.js/ts文件来运行添加自定义中间件。root在那里,您可以简单地检查 Auth-headers 并决定调用next以允许它或返回 401 状态代码以拒绝它。这是从 Nextjs 示例(打字稿)复制的示例

import { NextRequest, NextResponse } from 'next/server'

export const config = {
  matcher: '/',
}

export function middleware(req: NextRequest) {
  const basicAuth = req.headers.get('authorization')
  const url = req.nextUrl

  if (basicAuth) {
    const authValue = basicAuth.split(' ')[1]
    const [user, pwd] = atob(authValue).split(':')

    if (user === '4dmin' && pwd === 'testpwd123') {
      return NextResponse.next()
    }
  }
  url.pathname = '/api/auth'

  return NextResponse.rewrite(url)
}
Run Code Online (Sandbox Code Playgroud)

这是完整示例项目的链接: https://github.com/vercel/examples/tree/main/edge-functions/basic-auth-password

--- 对于旧版本(12.2 之前)---

_middleware.js/ts当前版本的 Nextjs 允许您通过目录中的文件来运行添加自定义中间件pages。在那里,您可以简单地检查 Auth-headers 并决定调用next以允许它或返回 401 状态代码以拒绝它。这是从 Nextjs 示例(打字稿)复制的示例

import { NextRequest, NextResponse } from 'next/server'

export function middleware(req: NextRequest) {
  const basicAuth = req.headers.get('authorization')

  if (basicAuth) {
    const auth = basicAuth.split(' ')[1]
    const [user, pwd] = Buffer.from(auth, 'base64').toString().split(':')

    if (user === '4dmin' && pwd === 'testpwd123') {
      return NextResponse.next()
    }
  }

  return new Response('Auth required', {
    status: 401,
    headers: {
      'WWW-Authenticate': 'Basic realm="Secure Area"',
    },
  })
}
Run Code Online (Sandbox Code Playgroud)

这是完整示例项目的链接: https://github.com/vercel/examples/tree/main/edge-functions/basic-auth-password


OZZ*_*ZIE 1

编辑:如果您想将其托管在 Vercel 上,那么下面的 ExpressJs 解决方案将无法工作,例如,它的成本有点高,但提供了最好的 Next.js 托管,具有内置边缘缓存以及图像缩放和优化功能。因此,如果您想将其托管在 Vercel 上,则可以使用以下方式向每个路由添加身份验证: https: //nextjs.org/docs/authentication(除非从项目一开始就完成,否则可能会很麻烦)。

然而,如果您打算将其托管在支持 Node.js 和 Express.js 托管的其他地方(并且没有为 Next.js 提供任何特殊功能),那么下面的解决方案是一个很好的解决方案。

ExpressJs解决方案:

本文不涉及基本身份验证凭据的存储或使用多个凭据。这更像是一个起点,因为我在谷歌上根本找不到任何东西。

在下面的两个示例中,我们将使用 Express.js 来托管 NextJS(在生产模式下!)。

在两个示例中启动服务器:(node index.mjs当前使用node -v 14.9.0)

首先:index.mjs在根目录中创建文件。

(选项 1)使用最新的 NextJS:

  1. 使用最新的(截至撰写时为 9.5)NextJS + Express.js 节点服务器最新的 nextjs 已修复问题(我尚未验证)与 CSP https://developer.mozilla.org/en-US/docs/Web/HTTP/光热发电
  2. 在index.mjs中添加以下代码(安装所需模块):
    import next from 'next'
    import express from 'express'

    import auth from 'basic-auth'

    const dev = process.env.NODE_ENV === 'development'
    const app = next({ dev })
    const handle = app.getRequestHandler()

    app.prepare()
      .then(() => {
        const server = express()

        server.use(function (req, res, next) {
          const credentials = auth(req)

          if (!credentials || credentials.name !== '[change_me_username]' || credentials.pass !== '[change_me_password]') {
            res.status(401)
            res.header('WWW-Authenticate', 'Basic realm="example"')
            res.send('Access denied')
          } else {
            next()
          }
        });

        server.get('*', (req, res) => {
          return handle(req, res)
        })

        const port = 80;
        server.listen(port, (err) => {
          if (err) {
            throw err
          }
          console.log(`Ready on http://localhost:${port}`)
        })
      })
      .catch((ex) => {
        console.error(ex.stack)
        process.exit(1)
      })
Run Code Online (Sandbox Code Playgroud)

(选项2)使用NextJS 9.4(如果无法升级)

此答案不涉及潜在 CSP 安全整体的潜在后果,处理该问题的最佳且最简单的方法是升级到 NextJs 9.5 版本。

  1. 在index.mjs中添加以下代码(安装所需模块):
    import next from 'next'
    import express from 'express'

    import auth from 'basic-auth'

    const dev = process.env.NODE_ENV === 'development'
    const app = next({ dev })
    const handle = app.getRequestHandler()

    app.prepare()
      .then(() => {
        const server = express()

        server.use(function (req, res, next) {
          const credentials = auth(req)

          if (!credentials || credentials.name !== '[change_me_username]' || credentials.pass !== '[change_me_password]') {
            res.status(401)
            res.header('WWW-Authenticate', 'Basic realm="example"')
            res.send('Access denied')
          } else {
            // this is different from above NextJS 9.5, 9.4 requires inline js.
            res.setHeader(
              'Content-Security-Policy',
              "default-src * 'self' data: 'unsafe-inline' *"
            );
            next()
          }
        });

        server.get('*', (req, res) => {
          return handle(req, res)
        })

        const port = 80;
        server.listen(port, (err) => {
          if (err) {
            throw err
          }
          console.log(`Ready on http://localhost:${port}`)
        })
      })
      .catch((ex) => {
        console.error(ex.stack)
        process.exit(1)
      })
Run Code Online (Sandbox Code Playgroud)
  1. next.config.js如果您使用,则在里面next-images,否则只需放置对象:module.exports = { // ... }
const withImages = require('next-images')
module.exports = withImages({
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            // // https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
            key: 'Content-Security-Policy',
            value: "default-src * 'self' data: 'unsafe-inline' *"
          }
        ]
      }
    ]
  }
})
Run Code Online (Sandbox Code Playgroud)