如何在Next.js的next-auth中保护路由?

19
我正在尝试在应用程序中使用next-auth库进行身份验证集成。我一直在遵循此处给出的官方教程:https://github.com/nextauthjs/next-auth-example/。给定示例的问题在于,我需要检查是否存在会话以便在每个需要身份验证的页面上使用。
    import { useState, useEffect } from 'react';
    import { useSession } from 'next-auth/client'
    
    export default function Page () {
      const [ session, loading ] = useSession()
      
      // Fetch content from protected route
      useEffect(()=>{
        const fetchData = async () => {
          const res = await fetch('/api/examples/protected')
          const json = await res.json()
        }
        fetchData()
      },[session])
    
      // When rendering client side don't display anything until loading is complete
      if (typeof window !== 'undefined' && loading) return null
    
      // If no session exists, display access denied message
      if (!session) { return  <Layout><AccessDenied/></Layout> }
    
      // If session exists, display content
      return (
        <Layout>
          <h1>Protected Page</h1>
          <p><strong>{content || "\u00a0"}</strong></p>
        </Layout>
      )
    }

或者像这样进行服务器端检查

    import { useSession, getSession } from 'next-auth/client'
    import Layout from '../components/layout'
    
    export default function Page () {
      // As this page uses Server Side Rendering, the `session` will be already
      // populated on render without needing to go through a loading stage.
      // This is possible because of the shared context configured in `_app.js` that
      // is used by `useSession()`.
      const [ session, loading ] = useSession()
    
      return (
        <Layout>
          <h1>Server Side Rendering</h1>
          <p>
            This page uses the universal <strong>getSession()</strong> method in <strong>getServerSideProps()</strong>.
          </p>
          <p>
            Using <strong>getSession()</strong> in <strong>getServerSideProps()</strong> is the recommended approach if you need to
            support Server Side Rendering with authentication.
          </p>
          <p>
            The advantage of Server Side Rendering is this page does not require client side JavaScript.
          </p>
          <p>
            The disadvantage of Server Side Rendering is that this page is slower to render.
          </p>
        </Layout>
      )
    }
    
    // Export the `session` prop to use sessions with Server Side Rendering
    export async function getServerSideProps(context) {
      return {
        props: {
          session: await getSession(context)
        }
      }
    }

这是一个很头疼的问题,因为我们需要在每个需要身份验证的页面上手动加入代码。有没有一种方法可以全局地检查给定的路由是否受保护,如果未登录则进行重定向,而不是在每个页面都写相同的代码?

3个回答

52

是的,您需要在每个页面上进行检查,您的逻辑是正确的(在认证状态可用之前显示旋转器),但是,您可以将身份验证状态向上提升,以便您不必为每个页面重复编写auth代码, _app组件非常适合这一点,因为它自然包装了所有其他组件(页面)。

      <AuthProvider>
        {/* if requireAuth property is present - protect the page */}
        {Component.requireAuth ? (
          <AuthGuard>
            <Component {...pageProps} />
          </AuthGuard>
        ) : (
          // public page
          <Component {...pageProps} />
        )}
      </AuthProvider>

AuthProvider组件包装了设置第三方提供者(Firebase、AWS Cognito、Next-Auth)的逻辑

AuthGuard是您放置身份验证检查逻辑的组件。您将注意到AuthGuard正在包装Component(实际上是Next.js框架中的页面)。因此,在查询身份验证提供程序时,AuthGuard将显示loading指示器,如果auth为true,则会显示Component,如果auth为false,则可以显示登录弹出窗口或重定向到登录页面。

关于Component.requireAuth,这是一个方便的属性,用于在每个页面上标记Component需要进行身份验证,如果该prop为false,则不会呈现AuthGuard

我已经详细介绍了这种模式:保护Next.js应用程序中的静态页面

我还制作了一个示例演示应用程序(源代码)


2
只是想说,这个答案非常棒,正是我已经寻找了很长时间的东西。谢谢@Ivan! - J. Jackson
@J.Jackson 谢谢!请确保查看代码存储库,我会不断更新它。 - Ivan V.
是的,这正是我一直在研究的内容!我正在努力删除一些硬编码的 auth.ts 代码,并修复我看到的一些 TS 严格错误。 - J. Jackson
@IvanV。我明白,但我只是想知道组件上的那个键(requireauth)来自哪里?它与getStaticProps有任何关系吗? - Kay
我猜现在你也可以通过 Next 12.0 中引入的中间件来解决这个问题? - antonwilhelm
显示剩余5条评论

5
我来晚了,但几天前我也遇到了这个问题。由于我使用的是Next.js v12.2+,所以我(以及next-auth)可以访问中间件。我将在下面发布我的中间件代码。
然而需要注意的是:next-auth仅支持使用JWT策略进行中间件身份验证检查。然而,我使用会话(sessions),而不是JWT。因此需要注意的是,我实际上并没有验证会话令牌。事实上,我在后端获取数据时对令牌进行了验证,因为所有用户数据都是通过该令牌检索的。 此中间件只执行一个操作,即如果未经身份验证的用户尝试访问需要进行身份验证的页面,则重定向他们到登录页面。精通技术的用户可能会手动添加cookie并能够到达页面,但这并不重要,因为他们将收到403或类似的错误,因为数据提取将失败(令牌无效,因此无法获取任何用户数据)。
我认为这种方法更快,因为你不必去获取你要进入的页面(如果它不是静态的,还需要渲染它),然后请求后端获取会话数据,再调用你的存储(数据库,redis等)来获取数据,并然后确定会话是否无效,最后才将用户重定向到登录页面。因此,在甚至未接触页面之前,中间件就已经知道没有会话,因此它(几乎)立即将用户重定向到登录页面。
以下是代码:
import { withAuth } from 'next-auth/middleware';

const publicFileRegex = /\.(.*)$/;
const anonymousRoutes = ['/', '/login', '/register', '/auth/error', '/auth/verify-request']; // The whitelisted routes

export default withAuth({
    callbacks: {
        authorized: ({ req }) => {
            const { pathname } = req.nextUrl;

            // Important! The below only checks if there exists a token. The token is not validated! This means
            // unauthenticated users can set a next-auth.session-token cookie and appear authorized to this
            // middleware. This is not a big deal because we do validate this cookie in the backend and load
            // data based off of its value. This middleware simply redirects unauthenticated users to the login
            // page (and sets a callbackUrl) for all routes, except static files, api routes, Next.js internals,
            // and the whitelisted anonymousRoutes above.
            return Boolean(
                req.cookies.get('next-auth.session-token') || // check if there's a token
                    pathname.startsWith('/_next') || // exclude Next.js internals
                    pathname.startsWith('/api') || //  exclude all API routes
                    pathname.startsWith('/static') || // exclude static files
                    publicFileRegex.test(pathname) || // exclude all files in the public folder
                    anonymousRoutes.includes(pathname)
            );
        },
    },
    // If you have custom pages like I do, these should be whitelisted!
    pages: {
        error: '/auth/error',
        signIn: '/login',
        verifyRequest: '/auth/verify-request',
    },
});

部分功劳归于这个讨论:https://github.com/vercel/next.js/discussions/38615


1
你可以使用 middleware.ts 或者 middleware.js 文件。在你的根目录下创建一个 middleware.js 文件,并粘贴以下代码。
export { default } from 'next-auth/middleware'


export const config = {
    matcher: ["/profile" ,"/", "/posts"]
}

在上述文件中,您可以指定要保护的路由。如果您不导出配置文件,中间件将适用于应用程序中的所有路由。它在路由到每个页面时检查会话。
当然,您需要在api/auth目录中拥有[...nextauth].js文件。该文件应包含提供者和回调函数(会话、jwt)。

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