使用Next.js进行Supabase认证

该子模块为在Next中实现用户身份验证提供了方便的帮助Next.js应用程序。

安装Next.js助手库#

1npm install @supabase/auth-helpers-nextjs

此库支持以下工具版本:

  • Node.js: ^10.13.0 || >=12.0.0
  • Next.js: >=10
  • 注:Next.js13除了新的app目录方法外,其他都受支持。我们正在努力增加对此的支持,您可以关注此处.

此外,为可在所有基于React的框架中使用的组件和挂钩安装React Auth Helpers

1npm install @supabase/auth-helpers-react

设置环境变量

在项目的API设置中检索项目URL和匿名密钥以设置以下环境变量。对于本地开发,可以在.env.local文件。参见示例

.env.local
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY

基本设置

包装pages/_app.js组件与SessionContextProvider组件:

pages/_app.js
1import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
2import { SessionContextProvider } from '@supabase/auth-helpers-react'
3
4function MyApp({ Component, pageProps }) {
5  const router = useRouter()
6  // Create a new supabase browser client on every first render.
7  const [supabaseClient] = useState(() => createBrowserSupabaseClient())
8
9  return (
10    <SessionContextProvider
11      supabaseClient={supabaseClient}
12      initialSession={pageProps.initialSession}
13    >
14      <Component {...pageProps} />
15    </SessionContextProvider>
16  )
17}

现在,您可以通过检查 useUser()钩子返回的user对象是否已定义来确定用户是否已通过身份验证。

使用TypeScript#

您可以将使用Supabase CLI生成的类型传递给Supabase客户端,以获得增强的类型安全性和自动完成:

浏览器客户端

创建新的Supabase数据库客户端对象:

1import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
2import { Database } from '../database.types'
3
4const supabaseClient = createBrowserSupabaseClient<Database>()

从SessionContext中检索超级客户端对象:

1import { useSupabaseClient } from '@supabase/auth-helpers-react'
2import { Database } from '../database.types'
3
4const supabaseClient = useSupabaseClient<Database>()

服务器客户端

1// Creating a new supabase server client object (e.g. in API route):
2import type { NextApiRequest, NextApiResponse } from 'next'
3import type { Database } from 'types_db'
4
5export default async (req: NextApiRequest, res: NextApiResponse) => {
6  const supabaseServerClient = createServerSupabaseClient<Database>({
7    req,
8    res,
9  })
10  const {
11    data: { user },
12  } = await supabaseServerClient.auth.getUser()
13
14  res.status(200).json({ name: user?.name ?? '' })
15}

使用RLS获取客户端数据#

为了使行级别安全在客户端获取数据时正常工作,您需要确保使用supabaseClient钩子中的 useSupabaseClient,并且仅在 useUser()钩子中定义了客户端用户后才运行查询:

1import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
2import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
3import { useEffect, useState } from 'react'
4
5const LoginPage = () => {
6  const supabaseClient = useSupabaseClient()
7  const user = useUser()
8  const [data, setData] = useState()
9
10  useEffect(() => {
11    async function loadData() {
12      const { data } = await supabaseClient.from('test').select('*')
13      setData(data)
14    }
15    // Only run query once user is logged in.
16    if (user) loadData()
17  }, [user])
18
19  if (!user)
20    return (
21      <Auth
22        redirectTo="http://localhost:3000/"
23        appearance={{ theme: ThemeSupa }}
24        supabaseClient={supabaseClient}
25        providers={['google', 'github']}
26        socialLayout="horizontal"
27      />
28    )
29
30  return (
31    <>
32      <button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
33      <p>user:</p>
34      <pre>{JSON.stringify(user, null, 2)}</pre>
35      <p>client-side data fetching with RLS</p>
36      <pre>{JSON.stringify(data, null, 2)}</pre>
37    </>
38  )
39}
40
41export default LoginPage

服务器端渲染 (SSR)#

创建服务器超级客户端以检索登录用户的会话:

pages/profile.js
1import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'
2
3export default function Profile({ user }) {
4  return <div>Hello {user.name}</div>
5}
6
7export const getServerSideProps = async (ctx) => {
8  // Create authenticated Supabase Client
9  const supabase = createServerSupabaseClient(ctx)
10  // Check if we have a session
11  const {
12    data: { session },
13  } = await supabase.auth.getSession()
14
15  if (!session)
16    return {
17      redirect: {
18        destination: '/',
19        permanent: false,
20      },
21    }
22
23  return {
24    props: {
25      initialSession: session,
26      user: session.user,
27    },
28  }
29}

使用RLS获取服务器端数据#

您可以使用服务器超级数据库客户端运行行级别安全性服务器端验证查询:

1import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'
2
3export default function ProtectedPage({ user, data }) {
4  return (
5    <>
6      <div>Protected content for {user.email}</div>
7      <pre>{JSON.stringify(data, null, 2)}</pre>
8      <pre>{JSON.stringify(user, null, 2)}</pre>
9    </>
10  )
11}
12
13export const getServerSideProps = async (ctx) => {
14  // Create authenticated Supabase Client
15  const supabase = createServerSupabaseClient(ctx)
16  // Check if we have a session
17  const {
18    data: { session },
19  } = await supabase.auth.getSession()
20
21  if (!session)
22    return {
23      redirect: {
24        destination: '/',
25        permanent: false,
26      },
27    }
28
29  // Run queries with RLS on the server
30  const { data } = await supabase.from('users').select('*')
31
32  return {
33    props: {
34      initialSession: session,
35      user: session.user,
36      data: data ?? [],
37    },
38  }
39}

使用 provider token将服务器端数据提取到OAuth API {#oauth-provider-token}#

当使用第三方身份验证提供程序时,会话将使用附加的 provider_token字段启动,该字段保存在身份验证cookie中,可以在会话对象中访问。provider_token可用于代表登录用户向OAuth提供程序的API端点发出API请求。

1import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'
2
3export default function ProtectedPage({ user, allRepos }) {
4  return (
5    <>
6      <div>Protected content for {user.email}</div>
7      <p>Data fetched with provider token:</p>
8      <pre>{JSON.stringify(allRepos, null, 2)}</pre>
9      <p>user:</p>
10      <pre>{JSON.stringify(user, null, 2)}</pre>
11    </>
12  )
13}
14
15export const getServerSideProps = async (ctx) => {
16  // Create authenticated Supabase Client
17  const supabase = createServerSupabaseClient(ctx)
18  // Check if we have a session
19  const {
20    data: { session },
21  } = await supabase.auth.getSession()
22
23  if (!session)
24    return {
25      redirect: {
26        destination: '/',
27        permanent: false,
28      },
29    }
30
31  // Retrieve provider_token & logged in user's third-party id from metadata
32  const { provider_token, user } = session
33  const userId = user.user_metadata.user_name
34
35  const allRepos = await (
36    await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, {
37      method: 'GET',
38      headers: {
39        Authorization: `token ${provider_token}`,
40      },
41    })
42  ).json()
43
44  return { props: { user, allRepos } }
45}

保护API路由#

创建服务器超级客户端以检索登录用户的会话:

pages/api/protected-route.js
1import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'
2
3const ProtectedRoute = async (req, res) => {
4  // Create authenticated Supabase Client
5  const supabase = createServerSupabaseClient({ req, res })
6  // Check if we have a session
7  const {
8    data: { session },
9  } = await supabase.auth.getSession()
10
11  if (!session)
12    return res.status(401).json({
13      error: 'not_authenticated',
14      description: 'The user does not have an active session or is not authenticated',
15    })
16
17  // Run queries with RLS on the server
18  const { data } = await supabase.from('test').select('*')
19  res.json(data)
20}
21
22export default ProtectedRoute

使用Next.js中间件进行身份验证。#

作为保护单个页面的替代方案,您可以使用Next.js中间件以保护整个目录或与配置对象匹配的目录。在以下示例中,所有对/middleware protected/*的请求都将检查用户是否已登录,如果成功,请求将被转发到目标路由,否则用户将被重定向:

middleware.ts
1import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
2import { NextResponse } from 'next/server'
3import type { NextRequest } from 'next/server'
4
5export async function middleware(req: NextRequest) {
6  // We need to create a response and hand it to the supabase client to be able to modify the response headers.
7  const res = NextResponse.next()
8  // Create authenticated Supabase Client.
9  const supabase = createMiddlewareSupabaseClient({ req, res })
10  // Check if we have a session
11  const {
12    data: { session },
13  } = await supabase.auth.getSession()
14
15  // Check auth condition
16  if (session?.user.email?.endsWith('@gmail.com')) {
17    // Authentication successful, forward request to protected route.
18    return res
19  }
20
21  // Auth condition not met, redirect to home page.
22  const redirectUrl = req.nextUrl.clone()
23  redirectUrl.pathname = '/'
24  redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname)
25  return NextResponse.redirect(redirectUrl)
26}
27
28export const config = {
29  matcher: '/middleware-protected',
30}

迁移指南

迁移到v0.5.X#

使这些助手更灵活,更易于维护,更易于为新版本的Next.js升级,我们将其分解为最有用的部分,即管理cookie,并在任何环境(客户端、服务器、中间件/边缘)中为您提供经过认证的超级js客户端。

因此,我们将withApiAuthwithPageAuthwithMiddlewareAuth高阶函数标记为已弃用,它们将在下一个次要版本(v0.6.X)中删除。

请按照以下步骤更新API路由、页面和中间件处理程序。谢谢

withApiAuth已弃用!

NextApiHandler中使用createServerSubaseClient

pages/api/protected-route.ts
1import { withApiAuth } from '@supabase/auth-helpers-nextjs'
2
3export default withApiAuth(async function ProtectedRoute(req, res, supabase) {
4  // Run queries with RLS on the server
5  const { data } = await supabase.from('test').select('*')
6  res.json(data)
7})

withPageAuth已弃用!

getServerSideProps中使用createServerSubaseClient

pages/profile.tsx
1import { withPageAuth, User } from '@supabase/auth-helpers-nextjs'
2
3export default function Profile({ user }: { user: User }) {
4  return <pre>{JSON.stringify(user, null, 2)}</pre>
5}
6
7export const getServerSideProps = withPageAuth({ redirectTo: '/' })

withMiddlewareAuth已弃用!

middleware.ts
1import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs'
2
3export const middleware = withMiddlewareAuth({
4  redirectTo: '/',
5  authGuard: {
6    isPermitted: async (user) => {
7      return user.email?.endsWith('@gmail.com') ?? false
8    },
9    redirectTo: '/insufficient-permissions',
10  },
11})
12
13export const config = {
14  matcher: '/middleware-protected',
15}

迁移到v0.4.X和supabase js v2#

随着supabase-js v2的更新,不再需要 authAPI路由,因此您可以继续删除 /pages/api/目录下的 auth目录。请参阅v2迁移指南以了解supabase js中的全部更改。

/api/auth/logout api路由已删除,请改用 signout 方法:

1<button
2  onClick={async () => {
3    await supabaseClient.auth.signOut()
4    router.push('/')
5  }}
6>
7  Logout
8</button>

已删除supabaseClientsupabaseServerClient,改用createBrowserSupbaseClientcreateServerSubaseClient方法。这允许您向客户端提供CLI生成的类型:

1// client-side
2import type { Database } from 'types_db'
3const [supabaseClient] = useState(() => createBrowserSupabaseClient<Database>())
4
5// server-side API route
6import type { NextApiRequest, NextApiResponse } from 'next'
7import type { Database } from 'types_db'
8
9export default async (req: NextApiRequest, res: NextApiResponse) => {
10  const supabaseServerClient = createServerSupabaseClient<Database>({
11    req,
12    res,
13  })
14  const {
15    data: { user },
16  } = await supabaseServerClient.auth.getUser()
17
18  res.status(200).json({ name: user?.name ?? '' })
19}
  • UserProvider 已被 SessionContextProvider替换。确保包装您的 pages/_app.jsSessionContextProvider兼容。然后,在整个应用程序中,您可以使用 useSessionContext钩子获取 session,使用useSubaseClient钩子获取经过身份验证的supbaseClient
  • useUser钩子现在返回user对象或null
  • TypeScript的用法:您可以将使用Suabase CLI生成的类型传递给Supabase客户端,以获得增强的类型安全性和自动完成:

创建新的超级数据库客户端对象:

1import { Database } from '../database.types'
2
3const [supabaseClient] = useState(() => createBrowserSupabaseClient<Database>())

从SessionContext中检索超级客户端对象:

1import { useSupabaseClient } from '@supabase/auth-helpers-react'
2import { Database } from '../database.types'
3
4const supabaseClient = useSupabaseClient<Database>()