认证路由

身份验证是 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)

非重定向认证

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

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 部分详细介绍 router.context 选项。

以下是一个使用 React context 和 hooks 来保护 TanStack Router 中已认证路由的示例。请在 Authenticated Routes 示例 中查看完整的可运行设置。

  • src/routes/__root.tsx
tsx
import { createRootRouteWithContext } from '@tanstack/solid-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/solid-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/solid-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/solid-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/solid-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/solid-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()** 到您的 **Login 路由**。

  • src/routes/dashboard.route.tsx
tsx
import { createFileRoute, redirect } from '@tanstack/solid-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/solid-router'

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

您还可以 **选择** 使用 非重定向认证 方法来显示登录表单,而不是调用 **redirect**。

此方法还可以与 Pathless 或 Layout Route 结合使用,以保护其父路由下的所有路由。

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

示例

存储库中提供了可用的身份验证示例

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

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

Bytes

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

订阅 Bytes

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

Bytes

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