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

haf*_*med 2 next.js next-auth

我正在尝试将身份验证与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>
      )
    }
Run Code Online (Sandbox Code Playgroud)

或者像这样用于服务器端检查

    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)
        }
      }
    }
Run Code Online (Sandbox Code Playgroud)

这是很多令人头痛的问题,因为我们需要在每个需要身份验证的页面上手动正确,有没有办法全局检查给定的路由是否受保护,如果未登录则重定向而不是在每个页面上都写这个?

Iva*_* V. 12

是的,您需要检查每个页面并且您的逻辑没问题(显示微调器直到身份验证状态可用)但是,您可以提升身份验证状态,因此您不必auth为每个页面重复代码, _app组件是一个完美的地方这是因为它自然地包装了所有其他组件(页面)。

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

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

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

关于Component.requireAuth这是一个方便的属性,它设置在每个页面上以将其标记Component为需要身份验证,如果该属性为 false,AuthGuard则永远不会呈现。

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

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

  • 只是想说,这个答案太棒了,这正是我长期以来一直在寻找的答案。谢谢@伊万! (2认同)

Vic*_*tor 5

我参加聚会迟到了,但几天前我自己也遇到了这个问题。由于我使用的是 Next.js v12.2+,因此我(和 next-auth)可以访问中间件。我将在下面发布我的中间件代码。

但有一个警告:next-auth 仅支持使用 JWT 策略进行中间件身份验证检查。然而,我使用会话;不是智威汤逊。需要注意的是,我实际上并没有验证会话令牌。问题是:我不需要。当我获取数据时,我会在后端验证令牌,因为所有用户数据都是通过该令牌检索的。该中间件只做一件事,如果未经身份验证的用户尝试访问需要身份验证的页面,则将其重定向到登录页面。精通技术的用户可能会自己添加 cookie 并且能够访问该页面,但这并不重要,因为他们会收到 403 或类似的错误,因为数据获取会失败(令牌无效,因此没有用户数据)可以获取)。

我认为这种方法更快,因为您不必获取您要访问的页面(如果它不是静态的,则渲染它),向后端发出请求以获取会话数据,调用您的商店(db, 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',
    },
});
Run Code Online (Sandbox Code Playgroud)

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