通过MemFire Cloud,开发者可以轻松地为WEB应用添加微信扫码登录功能,为用户提供更加流畅、安全的登录体验。

传送门

第一种方式(推荐使用)

此示例将为您设置一个非常常见的情况:用户可以注册或登录,然后使用公共个人资料信息(包括个人资料图片)更新其帐户。

此示例演示了:

  • 使用 Supabase Auth 进行微信扫码登录认证

    • Next.js 的 Supabase Auth 助手。
    • Supabase 为 React 预构建了 Auth UI。
  • 使用 MemFire Cloud 存储的用户头像图像

  • 受政策限制的公开资料。

  • 使用 Next.js 的前端。

示例代码

  git clone https://github.com/LucaRao/nextjs-user-management.git
  

第二种方式

1.安装 Supabase 依赖包。

安装 @supabase/supabase-js 包和帮助器 @supabase/ssr 包。

  npm install @supabase/supabase-js @supabase/ssr
  

2.设置环境变量

在项目根目录中创建一个 .env.local 文件。

填写您的 NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEY

  NEXT_PUBLIC_SUPABASE_URL=<your_supabase_project_url>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<your_supabase_anon_key>
  

3.编写实用函数来创建 Supabase 客户端

要从 Next.js 应用程序访问 Supabase,您需要 2 种类型的 Supabase 客户端:

  • 客户端组件客户端 - 从在浏览器中运行的客户端组件访问 Supabase。
  • 服务器组件客户端 - 从仅在服务器上运行的服务器组件、服务器操作和路由处理程序访问 Supabase。

创建一个 utils/supabase 文件夹,其中包含每种类型客户端的文件。然后复制每种客户端类型的实用函数。

utils/supabase/client.ts

  import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}
  

utils/supabase/server.ts

  import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { cookies } from 'next/headers'

export function createClient() {
  const cookieStore = cookies()

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value
        },
        set(name: string, value: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value, ...options })
          } catch (error) {
            // The `set` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
        remove(name: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value: '', ...options })
          } catch (error) {
            // The `delete` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
      },
    }
  )
}
  

4.连接中间件

在项目的根目录下创建 middleware.ts 文件。由于服务器组件无法写入 cookie,因此您需要中间件来刷新过期的身份验证令牌并存储它们。

中间件负责:

  • 刷新身份验证令牌(通过调用supabase.auth.getUser)。
  • 将刷新的身份验证令牌传递给服务器组件,这样它们就不会尝试自己刷新相同的令牌。这是通过 request.cookies.set 完成的。
  • 将刷新的身份验证令牌传递给浏览器,以便它替换旧令牌。这是通过response.cookies.set 完成的。

复制您的应用程序的中间件代码。

middleware.ts

  import { type NextRequest } from 'next/server'
import { updateSession } from '@/utils/supabase/middleware'

export async function middleware(request: NextRequest) {
  return await updateSession(request)
}

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     * Feel free to modify this pattern to include more paths.
     */
    '/((?!_next/static|_next/image|favicon.ico|.*\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
}
  

utils/supabase/middleware.ts

  import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function updateSession(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  })

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return request.cookies.get(name)?.value
        },
        set(name: string, value: string, options: CookieOptions) {
          request.cookies.set({
            name,
            value,
            ...options,
          })
          response = NextResponse.next({
            request: {
              headers: request.headers,
            },
          })
          response.cookies.set({
            name,
            value,
            ...options,
          })
        },
        remove(name: string, options: CookieOptions) {
          request.cookies.set({
            name,
            value: '',
            ...options,
          })
          response = NextResponse.next({
            request: {
              headers: request.headers,
            },
          })
          response.cookies.set({
            name,
            value: '',
            ...options,
          })
        },
      },
    }
  )

  await supabase.auth.getUser()

  return response
}
  

5.配置重定向

在MemFire Cloud应用界面进行重定向URL配置

6.创建登录页面

为您的应用程序创建登录页面。使用服务器操作调用 MemFire Cloud 微信扫码登录api。

由于 api 是从 Action 中调用的,因此请使用 @/utils/supabase/server.ts 中定义的客户端。

app/login/page.tsx

  import { login } from "./actions";

export default function LoginPage() {
  return (
    <div className="min-h-screen flex items-center justify-center">
      <form>
        <button
          className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
          formAction={login}
        >
          微信登录
        </button>
      </form>
    </div>
  );
}
  

app/login/actions.tsx

对于 PKCE 流程,例如在服务器端身份验证中,您需要一个额外的步骤来处理代码交换。调用signInWithOAuth 时,提供指向回调路由的redirectTo URL。应将此重定向 URL 添加到您的重定向允许列表中。

在服务器中,您需要处理到 OAuth 提供程序的身份验证端点的重定向。 SignInWithOAuth 方法返回您可以重定向到的端点 URL。

这里的redirectTo需要与在MemFire Cloud应用界面进行重定向的URL中的某一个保持一致

  'use server'

import { redirect } from 'next/navigation'

import { createClient } from '@/utils/supabase/server'

export async function login() {
  const supabase = createClient()
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: 'wechat_qr',
    options: {
        redirectTo: 'http://localhost:3000/private',
    },
    
})
if (data.url) {
  redirect(data.url) // use the redirect API for your server framework
}
}
  

app/error/page.tsx

  export default function ErrorPage() {
    return <div className="min-h-screen flex items-center justify-center">
    <p>对不起,出错了</p>
  </div> 
  }
  

请注意,在调用 Supabase服务 之前会调用 cookie,Supabase服务 会选择从 Next.js 的缓存中提取调用。这对于经过身份验证的数据获取非常重要,以确保用户只能访问自己的数据。

7.更改Auth确认路径

在回调端点,处理代码交换以保存用户会话。

app\private\route.ts

  import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
import { type CookieOptions, createServerClient } from '@supabase/ssr'

export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url)
  const code = searchParams.get('code')
  // if "next" is in param, use it as the redirect URL
  const next = searchParams.get('next') ?? '/'

  if (code) {
    const cookieStore = cookies()
    const supabase = createServerClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
      {
        cookies: {
          get(name: string) {
            return cookieStore.get(name)?.value
          },
          set(name: string, value: string, options: CookieOptions) {
            cookieStore.set({ name, value, ...options })
          },
          remove(name: string, options: CookieOptions) {
            cookieStore.delete({ name, ...options })
          },
        },
      }
    )
    const { error } = await supabase.auth.exchangeCodeForSession(code)
    if (!error) {
      return NextResponse.redirect(`${origin}${next}`)
    }
  }

  // return the user to an error page with instructions
  return NextResponse.redirect(`${origin}/error`)
}
  

8.从服务器组件访问用户信息

服务器组件可以读取cookie,因此您可以获得身份验证状态和用户信息。

由于您是从服务器组件调用 Supabase服务,因此请使用在 @/utils/supabase/server.ts 中创建的客户端。

创建一个私人页面,用户只有登录后才能访问。该页面显示他们的电子邮件。

app\page.tsx

  import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";

export default async function Home() {
  const supabase = createClient()

    const { data, error } = await supabase.auth.getUser()
    if (error || !data?.user) {
      redirect('/login')
    }
  return (
    <main className="flex min-h-screen flex-col items-center  p-24">
      <p>Hello 你的用户名为:{data.user.user_metadata.full_name}</p>
      <p>你的用户id为:{data.user.id}</p>
      <form action="/signout" method="post">
          <button className="button block" type="submit">
            退出登录
          </button>
        </form>
    </main>
  );
}
  

9.退出登录

需要从服务器退出登录,清除用户会话。

app\signout\route.ts

  import { type CookieOptions, createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { type NextRequest, NextResponse } from 'next/server'

export async function POST(req: NextRequest) {
    const cookieStore = cookies()
    const supabase = createServerClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL!,
        process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
        {
          cookies: {
            get(name: string) {
              return cookieStore.get(name)?.value
            },
            set(name: string, value: string, options: CookieOptions) {
              cookieStore.set({ name, value, ...options })
            },
            remove(name: string, options: CookieOptions) {
              cookieStore.delete({ name, ...options })
            },
          },
        }
      )

  // Check if we have a session
  const {
    data: { session },
  } = await supabase.auth.getSession()

  if (session) {
    await supabase.auth.signOut()
  }

  return NextResponse.redirect(new URL('/login', req.url), {
    status: 302,
  })
}