0


Next.js使用Supabase实现Github登录

Next.js 有许多 OAuth 认证方案来实现 Github 或者 Google 登录,比较常见的有 next-auth、clerk、supabase等。Supabase提供了很多的核心服务,包括 PostgreSQL 数据库、身份验证、文件存储等。

本文将介绍如何使用 Supabase 实现 Github 登录,您将学到:

  1. 使用 OAuth 认证登录。
  2. 使用 Github 注册自动创建用户表数据。
  3. 用户数据缓存(zustand)。
  4. 路由守卫。

在继续开始前,您需要具备以下的基本知识:

  • Node.js
  • npm/pnpm
  • Next.js

起步

项目初始化

使用 pnpm 创建最新的 Next.js 项目。

Node.js 版本至少需要 v18.17。

PS J:\next-project>pnpm create create-next-app@latest
√ What is your project named? ... next-auth
√ Would you like to use TypeScript? ... No / Yes
√ Would you like to use ESLint? ... No / Yes
√ Would you like to use Tailwind CSS? ... No / Yes
√ Would you like to use `src/` directory? ... No / Yes
√ Would you like to use App Router? (recommended)... No / Yes
√ Would you like to customize the default importalias(@/*)? ... No / Yes

dependencies:
+ next 14.0.3
+ react 18.2.0
+ react-dom 18.2.0

devDependencies:
+ @types/node 20.10.3
+ @types/react 18.2.41
+ @types/react-dom 18.2.17
+ autoprefixer 10.4.16
+ eslint 8.55.0
+ eslint-config-next 14.0.3
+ postcss 8.4.32
+ tailwindcss 3.3.5
+ typescript 5.3.2

2023-12-04_11-14-27

在终端启动项目:

pnpm run dev

浏览器打开 http://localhost:3000/ 将看到:

2023-12-04_11-56-47

创建 Supabase 项目

  1. 首先进入 supabase 创建一个账户。
  2. 登录成功后进入 dashboard ,点击 New project。
  3. 设置项目名、数据库密码以及所属地区。

开始

为了实现一个好看的页面,我这里将使用 shadcn-ui 来作为项目的 ui 组件库。

安装 shadcn-ui:

PS J:\next-project\next-auth>pnpm dlx shadcn-ui@latest init
√ Would you like to use TypeScript (recommended)? ... no / yes
√ Which style would you like to use? » New York
√ Which color would you like to use as base color? » Zinc
√ Where is your global CSS file? ... app/globals.css
√ Would you like to use CSS variables for colors? ... no / yes
√ Where is your tailwind.config.js located? ... tailwind.config.ts
√ Configure the importaliasfor components: ... @/components
√ Configure the importaliasfor utils: ... @/lib/utils
√ Are you using React Server Components? ... no / yes
√ Write configuration to components.json. Proceed? ... yes

2023-12-04_12-04-07.png

添加 Button 按钮:

pnpm dlx shadcn-ui@latest add button

添加 Lucide 图标库:

pnpminstall lucide-react

修改

app/page.tsx

import { Button } from "@/components/ui/button";
import { Github } from "lucide-react";

export default function Home() {
  return (
    <div>
      <Button className="flex items-center gap-1">
        <Github size={18} />
        Login
      </Button>
    </div>
  );
}

在 Next.js 中使用 Supabase

安装 Supabase 包

pnpminstall @supabase/ssr @supabase/supabase-js

在项目根目录新建一个

.env.local

文件,SUPABASE_URL 和 SUPABASE_ANON_KEY 可以在 https://supabase.com/dashboard/project/_/settings/api 中获取。

NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key

2023-12-04_12-24-44.png

在根目录新建

middleware.ts

文件。输入以下内容:

import{ createServerClient,typeCookieOptions}from'@supabase/ssr'import{ NextResponse,typeNextRequest}from'next/server'exportasyncfunctionmiddleware(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,},})},remove(name:string, options: CookieOptions){
          request.cookies.set({
            name,
            value:'',...options,})
          response = NextResponse.next({
            request:{
              headers: request.headers,},})},},})await supabase.auth.getSession()return response
}

新建

/app/auth/callback/route.ts

文件:

import{ cookies }from"next/headers";import{ NextResponse }from"next/server";import{typeCookieOptions, createServerClient }from"@supabase/ssr";exportasyncfunctionGET(request: Request){const{ searchParams, origin }=newURL(request.url);const code = searchParams.get("code");// if "next" is in param, use it as the redirect URLconst 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 instructionsreturn NextResponse.redirect(`${origin}/auth/auth-code-error`);}

添加 Supabase 的 Auth Provider.

  1. 访问 https://supabase.com/dashboard/project/_/auth/providers,找到 Github,开启。
  2. 访问 https://github.com/settings/developers,点击 New Oauth App,Homepage URL 填入 http://localhost:3000/ , Authorization callback URL 填入 Supabase 提供的 Callback URL (for OAuth),点击 Register Application。
  3. 点击 Generate a new client secret,复制秘钥。
  4. 将 Client ID 和 Client Secret 分别填入,点击 Save。

2023-12-04_13-47-07.png

2023-12-04_13-40-16.png

2023-12-04_13-42-32.png

修改

app/page.tsx

"use client";

import { createBrowserClient } from "@supabase/ssr";
import { Github } from "lucide-react";

import { Button } from "@/components/ui/button";

export default function Home() {
    const pathname = usePathname;

  const supabase = createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );

  const handleLogin = async () => {
    await supabase.auth.signInWithOAuth({
      provider: "github",
      options: {
        redirectTo: location.origin + "/auth/callback?next=" + pathname,
      },
    });
  };

  const handleLogout = async () => {
    await supabase.auth.signOut();
  };

  return (
    <div className="flex gap-2">
      <Button onClick={handleLogin} className="flex items-center gap-1">
        <Github size={18} />
        Login
      </Button>
      <Button onClick={handleLogout} className="flex items-center gap-1">
              <LogOut size={18} />
                Logout
            </Button>
    </div>
  );
}

点击登录按钮,认证成功将返回首页

http://localhost:3000

,此时我们已经完成了最基础的登录登出功能。

2023-12-04_14-17-06.png

用户信息缓存

安装 zustand

pnpminstall zustand

创建文件

/lib/store/user.ts

import{ create }from"zustand";import{ User }from"@supabase/supabase-js";interfaceUserState{
  user: User |undefined;setUser:(user: User |undefined)=>void;}exportconst useUser =create<UserState>((set)=>({
  user:undefined,setUser:(user)=>set(()=>({ user })),}));

创建文件

/components/session-provider.tsx

"use client";

import { useUser } from "@/lib/store/user";
import { createBrowserClient } from "@supabase/ssr";
import { useCallback, useEffect } from "react";

const SessionProvider = () => {
  const setUser = useUser((state) => state.setUser);

  const supabase = createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );

  const userSession = useCallback(async () => {
    const { data } = await supabase.auth.getSession();
    setUser(data.session?.user);
  }, [setUser, supabase]);

  useEffect(() => {
    userSession();
  }, [userSession]);

  return null
};

export default SessionProvider;

/app/layout.tsx

中引入 session-provider。

import SessionProvider from "@/components/session-provider";
...
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        {children}
        <SessionProvider />
      </body>
    </html>
  );
}

修改

/app/page.tsx

"use client";

import { createBrowserClient } from "@supabase/ssr";
import { Github, LogOut } from "lucide-react";

import { Button } from "@/components/ui/button";
import { useUser } from "@/lib/store/user";

export default function Home() {
    const pathname = usePathname;

  const user = useUser((state) => state.user);
  const setUser = useUser((state) => state.setUser);

  console.log(user, "user");

  const supabase = createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );

  const handleLogin = async () => {
    await supabase.auth.signInWithOAuth({
      provider: "github",
      options: {
        redirectTo: location.origin + "/auth/callback?next=" + pathname,
      },
    });
  };

  const handleLogout = async () => {
    await supabase.auth.signOut();
    setUser(undefined);
  };

  return (
    <div>
      <h1 className="text-2xl py-2">Hi: {user?.user_metadata?.user_name}</h1>
      <div className="flex gap-2">
        {!user?.id ? (
          <Button onClick={handleLogin} className="flex items-center gap-1">
            <Github size={18} />
            Login
          </Button>
        ) : (
          <Button onClick={handleLogout} className="flex items-center gap-1">
            <LogOut size={18} />
            Logout
          </Button>
        )}
      </div>
    </div>
  );
}

同步数据表

访问 https://supabase.com/dashboard/project/_/database/tables,点击 New table,创建一张 users 数据表。

2023-12-04_16-38-27.png

创建完成后进入 SQL Editor 填入下面两个 SQL,执行。

-- 创建 create_user_on_signup 函数,1.在 public.users 表中插入一条新的记录。2.更新 auth.users 表中的 raw_user_meta_data 字段。CREATEFUNCTION create_user_on_signup()RETURNSTRIGGERAS $$
BEGININSERTINTOpublic.users (id, email, user_name, image_url)VALUES(
      NEW.id,
      NEW.raw_user_meta_data ->>'email',
      NEW.raw_user_meta_data ->>'user_name',
      NEW.raw_user_meta_data ->>'avatar_url');UPDATE auth.users SET raw_user_meta_data = raw_user_meta_data ||'{"role": "user"}'::jsonb WHERE auth.users.id = NEW.id;RETURN NEW;END;
$$ language plpgsql security definer;-- 创建触发器,当 auth.users 表新增用户后自动触发 create_user_on_signup 函数CREATETRIGGER create_user_on_signup afterINSERTON auth.users FOR EACH ROWEXECUTEFUNCTION create_user_on_signup();
  1. 进入 Authentication 将已经授权的用户删除
  2. 重新点击登录。此时 users 表已经同步新增了一条数据。

路由守卫

我们的一些页面是不希望未登录用户或者普通用户进行访问的,于是需要对页面进行拦截。

修改

middleware.ts

文件:

exportasyncfunctionmiddleware(request: NextRequest){...const{ data }=await supabase.auth.getSession();if(!data.session || data.session.user.user_metadata.role !=='admin'){return NextResponse.redirect(newURL('/', request.url));}return response;}exportconst config ={
  matcher:["/admin/:path*"],};

新建

/app/admin/page.ts

const AdminPage = () => {
  return <div>admin page</div>;
};

export default AdminPage;

此时访问 http://localhost:3000/admin 会被重定向到首页。

进入 SQL Editor 修改我们的权限:

UPDATE users SET role ='admin'WHERE id ='254ec4d1-a5bb-46de-9a29-134aa59ddfcb';UPDATE auth.users SET raw_user_meta_data = raw_user_meta_data ||'{"role": "admin"}'::jsonb WHERE auth.users.id ='254ec4d1-a5bb-46de-9a29-134aa59ddfcb';

id 可以在 user 表或者 Authentication 页面中复制。

退出重新登录,再次访问 /admin 页面,成功进入。


本文转载自: https://blog.csdn.net/qq_44600038/article/details/134875083
版权归原作者 远小帅 所有, 如有侵权,请联系我们删除。

“Next.js使用Supabase实现Github登录”的评论:

还没有评论