框架
版本

认证路由

身份验证是 Web 应用程序极其常见的需求。本指南将介绍如何使用 TanStack Router 构建受保护的路由,以及如果用户尝试访问它们时如何将他们重定向到登录页面。

route.beforeLoad 选项

route.beforeLoad 选项允许您指定一个函数,该函数将在路由加载之前被调用。它接收与 route.loader 函数相同的所有参数。这是检查用户是否已通过身份验证的好地方,如果未通过身份验证,则将其重定向到登录页面。

beforeLoad 函数按相对于这些其他路由加载函数的顺序运行

  • 路由匹配(自上而下)
    • route.params.parse
    • route.validateSearch
  • 路由加载(包括预加载)
    • route.beforeLoad
    • route.onError
  • 路由加载(并行)
    • route.component.preload?
    • route.load

重要的是要知道,路由的 beforeLoad 函数是在其任何子路由的 beforeLoad 函数之前被调用的。 它本质上是路由及其所有子路由的中间件函数。

如果您在 beforeLoad 中抛出错误,则其任何子路由都不会尝试加载。.

重定向

虽然不是必需的,但某些身份验证流程需要重定向到登录页面。为此,您可以从 beforeLoad 抛出 redirect()

tsx
// src/routes/_authenticated.tsx
export const Route = createFileRoute('/_authenticated')({
  beforeLoad: async ({ location }) => {
    if (!isAuthenticated()) {
      throw redirect({
        to: '/login',
        search: {
          // Use the current location to power a redirect after login
          // (Do not use `router.state.resolvedLocation` as it can
          // potentially lag behind the actual current location)
          redirect: location.href,
        },
      })
    }
  },
})
// src/routes/_authenticated.tsx
export const Route = createFileRoute('/_authenticated')({
  beforeLoad: async ({ location }) => {
    if (!isAuthenticated()) {
      throw redirect({
        to: '/login',
        search: {
          // Use the current location to power a redirect after login
          // (Do not use `router.state.resolvedLocation` as it can
          // potentially lag behind the actual current location)
          redirect: location.href,
        },
      })
    }
  },
})

提示

redirect() 函数接受与 navigate 函数相同的所有选项,因此您可以传递 replace: true 等选项,如果您想替换当前历史记录条目而不是添加新条目。

一旦您验证了用户身份,通常的做法是将其重定向回他们尝试访问的页面。为此,您可以使用我们在原始重定向中添加的 redirect 搜索参数。由于我们将用原始 URL 替换整个 URL,因此 router.history.pushrouter.navigate 更适合此目的。

tsx
router.history.push(search.redirect)
router.history.push(search.redirect)

非重定向身份验证

一些应用程序选择不将用户重定向到登录页面,而是将用户保留在同一页面上并显示一个登录表单,该表单要么替换主内容,要么通过模态框隐藏它。使用 TanStack Router 也可以实现这一点,只需简单地短路渲染通常会渲染子路由的 <Outlet /> 即可。

tsx
// src/routes/_authenticated.tsx
export const Route = createFileRoute('/_authenticated')({
  component: () => {
    if (!isAuthenticated()) {
      return <Login />
    }

    return <Outlet />
  },
})
// src/routes/_authenticated.tsx
export const Route = createFileRoute('/_authenticated')({
  component: () => {
    if (!isAuthenticated()) {
      return <Login />
    }

    return <Outlet />
  },
})

这将使您将用户保留在同一页面上,但仍然可以渲染登录表单。一旦用户通过身份验证,您只需渲染 <Outlet />,子路由就会被渲染。

使用 React context/hooks 进行身份验证

如果您的身份验证流程依赖于与 React context 和/或 hooks 的交互,您需要使用 router.context 选项将您的身份验证状态传递给 TanStack Router。

重要

React hooks 不应该在 React 组件之外使用。如果您需要在 React 组件之外使用 hook,您需要在包装您的 <RouterProvider /> 的组件中从 hook 中提取返回的状态,然后将返回的值传递给 TanStack Router。

我们将在 路由器上下文 部分详细介绍 router.context 选项。

这是一个使用 React 上下文和 hooks 保护 TanStack Router 中经过身份验证的路由的示例。请参阅 身份验证路由示例 中的完整工作设置。

  • src/routes/__root.tsx
tsx
import { createRootRouteWithContext } from '@tanstack/react-router'

interface MyRouterContext {
  // The ReturnType of your useAuth hook or the value of your AuthContext
  auth: AuthState
}

export const Route = createRootRouteWithContext<MyRouterContext>()({
  component: () => <Outlet />,
})
import { createRootRouteWithContext } from '@tanstack/react-router'

interface MyRouterContext {
  // The ReturnType of your useAuth hook or the value of your AuthContext
  auth: AuthState
}

export const Route = createRootRouteWithContext<MyRouterContext>()({
  component: () => <Outlet />,
})
  • src/router.tsx
tsx
import { createRouter } from '@tanstack/react-router'

import { routeTree } from './routeTree.gen'

export const router = createRouter({
  routeTree,
  context: {
    // auth will initially be undefined
    // We'll be passing down the auth state from within a React component
    auth: undefined!,
  },
})
import { createRouter } from '@tanstack/react-router'

import { routeTree } from './routeTree.gen'

export const router = createRouter({
  routeTree,
  context: {
    // auth will initially be undefined
    // We'll be passing down the auth state from within a React component
    auth: undefined!,
  },
})
  • src/App.tsx
tsx
import { RouterProvider } from '@tanstack/react-router'

import { AuthProvider, useAuth } from './auth'

import { router } from './router'

function InnerApp() {
  const auth = useAuth()
  return <RouterProvider router={router} context={{ auth }} />
}

function App() {
  return (
    <AuthProvider>
      <InnerApp />
    </AuthProvider>
  )
}
import { RouterProvider } from '@tanstack/react-router'

import { AuthProvider, useAuth } from './auth'

import { router } from './router'

function InnerApp() {
  const auth = useAuth()
  return <RouterProvider router={router} context={{ auth }} />
}

function App() {
  return (
    <AuthProvider>
      <InnerApp />
    </AuthProvider>
  )
}

然后,在已认证的路由中,您可以使用 beforeLoad 函数检查认证状态,如果用户未登录,则抛出 redirect() 到您的登录路由

  • src/routes/dashboard.route.tsx
tsx
import { createFileRoute, redirect } from '@tanstack/react-router'

export const Route = createFileRoute('/dashboard')({
  beforeLoad: ({ context, location }) => {
    if (!context.auth.isAuthenticated) {
      throw redirect({
        to: '/login',
        search: {
          redirect: location.href,
        },
      })
    }
  },
})
import { createFileRoute, redirect } from '@tanstack/react-router'

export const Route = createFileRoute('/dashboard')({
  beforeLoad: ({ context, location }) => {
    if (!context.auth.isAuthenticated) {
      throw redirect({
        to: '/login',
        search: {
          redirect: location.href,
        },
      })
    }
  },
})

可以选择使用非重定向身份验证方法显示登录表单,而不是调用重定向

此方法还可以与无路径或布局路由结合使用,以保护其父路由下的所有路由。

有关详细的分步实施指南,请参阅

示例

可用的身份验证示例在仓库中

我们的合作伙伴
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。

订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。