Next.js路由中间件用于身份验证

26
我正在尝试找到一种适当的身份验证方式,这在GitHub问题页面上是一个敏感话题。
我的身份验证很简单。我将JWT令牌存储在会话中,并将其发送到另一台服务器进行批准。如果返回true,则继续进行,如果返回false,则清除会话并将其发送到主页。
在我的server.js文件中,我有以下内容(注意-我使用了Next.js学习示例并只添加了isAuthenticated):
function isAuthenticated(req, res, next) {
  //checks go here

  //if (req.user.authenticated)
   // return next();

  // IF A USER ISN'T LOGGED IN, THEN REDIRECT THEM SOMEWHERE
  res.redirect('/');
}

server.get('/p/:id', isAuthenticated, (req, res) => {
  const actualPage = '/post'
  const queryParams = { id: req.params.id }
  app.render(req, res, actualPage, queryParams)
})

这是按照设计运行的。如果我刷新页面/p/123,它将重定向到/。但是,如果我通过next/link href访问该页面,则不会重定向,因为此时它没有使用 express 而是使用了 next 的自定义路由。
是否有一种方法可以为每个未经过 express 的 next/link 添加检查,以便确保用户已登录?
3个回答

26

来自下一个聊天的Tim帮我解决了这个问题。解决方案可以在这里找到,但是我会引用他的话,让大家都能看到:

我还创建了一个示例骨架模板供您参考。

--

编辑于2021年7月 - 警告:这是一种过时的解决方案,并且尚未确认与最新版本的Next.js兼容。使用骨架模板需自担风险。


16
所有链接均已失效 :( - softmarshmallow
1
链接出现了404错误。 - Mohammad Reza

13

编辑:更新了Next 12.2+的答案。

注意:以下内容摘自官方博客文章,因为SO通常不鼓励链接会随着时间变得过时/失效。https://nextjs.org/blog/next-12-2#middleware-stable

中间件现在稳定在12.2版本,并且基于用户反馈,具有改进的API。

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

// If the incoming request has the "beta" cookie
// then we'll rewrite the request to /beta
export function middleware(req: NextRequest) {
  const isInBeta = JSON.parse(req.cookies.get('beta') || 'false');
  req.nextUrl.pathname = isInBeta ? '/beta' : '/';
  return NextResponse.rewrite(req.nextUrl);
}

// Supports both a single value or an array of matches
export const config = {
  matcher: '/',
};

迁移指南

https://nextjs.org/docs/messages/middleware-upgrade-guide 重大变更

  • 不支持嵌套中间件
  • 无响应体
  • Cookie API已升级
  • 新增User-Agent助手
  • 不再有页面匹配数据
  • 在内部Next.js请求上执行中间件

如何升级

您应该在应用程序中声明一个单一的中间件文件,该文件应该位于pages目录旁边,并且没有_前缀。您的中间件文件仍然可以具有.ts.js扩展名。

中间件将为应用程序中的每个路由调用,并且可以使用自定义匹配器来定义匹配过滤器。以下是一个中间件的示例,触发/about/*/dashboard/:path*路由的自定义匹配器定义在导出的配置对象中:

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  return NextResponse.rewrite(new URL('/about-2', request.url))
}

// Supports both a single string value or an array of matchers
export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}

编辑: 适用于Next > 12和 < 12.2的过时答案

随着Next.js 12的发布,现在有beta版支持使用Vercel Edge Functions的中间件。

https://nextjs.org/blog/next-12#introducing-middleware

中间件使用严格的运行时环境,支持标准Web API,如fetch。使用next start即可立即实现,也可以在使用Edge函数的平台(如Vercel)上进行。

要在Next.js中使用Middleware,您可以创建文件pages/_middleware.js。在本示例中,我们使用了标准Web API响应(MDN):

// pages/_middleware.js

export function middleware(req, ev) {
  return new Response('Hello, world!')
}

JWT身份验证示例

next.config.js中:

const withTM = require('@vercel/edge-functions-ui/transpile')()

module.exports = withTM()

pages/_middleware.js 中:

import { NextRequest, NextResponse } from 'next/server'
import { setUserCookie } from '@lib/auth'

export function middleware(req: NextRequest) {
  // Add the user token to the response
  return setUserCookie(req, NextResponse.next())
}

pages/api/_middleware.js文件中:

import type { NextRequest } from 'next/server'
import { nanoid } from 'nanoid'
import { verifyAuth } from '@lib/auth'
import { jsonResponse } from '@lib/utils'

export async function middleware(req: NextRequest) {
  const url = req.nextUrl

  if (url.searchParams.has('edge')) {
    const resOrPayload = await verifyAuth(req)

    return resOrPayload instanceof Response
      ? resOrPayload
      : jsonResponse(200, { nanoid: nanoid(), jwtID: resOrPayload.jti })
  }
}

pages/api/index.js 中:
import type { NextApiRequest, NextApiResponse } from 'next'
import { verify, JwtPayload } from 'jsonwebtoken'
import { nanoid } from 'nanoid'
import { USER_TOKEN, JWT_SECRET_KEY } from '@lib/constants'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'GET') {
    return res.status(405).json({
      error: { message: 'Method not allowed' },
    })
  }
  try {
    const token = req.cookies[USER_TOKEN]
    const payload = verify(token, JWT_SECRET_KEY) as JwtPayload
    res.status(200).json({ nanoid: nanoid(), jwtID: payload.jti })
  } catch (err) {
    res.status(401).json({ error: { message: 'Your token has expired.' } })
  }
}

嵌套中间件不再受支持,因此在后续版本上将无法使用。 - NotoriousPyro
1
谢谢!我已经更新了,包括12.2公告博客文章和官方迁移指南中的最新指导。 - Nate Radebaugh
读取中间件中的 cookie,就像你所说的那样,不起作用。https://dev59.com/c2SJtIgBPY-HTNNj1IZm - AR Second

3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接