Next.js 有许多 OAuth 认证方案来实现 Github 或者 Google 登录,比较常见的有 next-auth、clerk、supabase等。Supabase提供了很多的核心服务,包括 PostgreSQL 数据库、身份验证、文件存储等。
本文将介绍如何使用 Supabase 实现 Github 登录,您将学到:
- 使用 OAuth 认证登录。
- 使用 Github 注册自动创建用户表数据。
- 用户数据缓存(zustand)。
- 路由守卫。
在继续开始前,您需要具备以下的基本知识:
- 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 import alias (@/*)? ... 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
在终端启动项目:
pnpm run dev
浏览器打开 http://localhost:3000/ 将看到:
创建 Supabase 项目
- 首先进入 supabase 创建一个账户。
- 登录成功后进入 dashboard ,点击 New project。
- 设置项目名、数据库密码以及所属地区。
开始
为了实现一个好看的页面,我这里将使用 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 import alias for components: ... @/components
√ Configure the import alias for utils: ... @/lib/utils
√ Are you using React Server Components? ... no / yes
√ Write configuration to components.json. Proceed? ... yes
添加 Button 按钮:
pnpm dlx shadcn-ui@latest add button
添加 Lucide 图标库:
pnpm install 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 包
pnpm install @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
在根目录新建 middleware.ts
文件。输入以下内容:
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function middleware(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 { 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}/auth/auth-code-error`);
}
添加 Supabase 的 Auth Provider.
- 访问 https://supabase.com/dashboard/project/_/auth/providers,找到 Github,开启。
- 访问 https://github.com/settings/developers,点击 New Oauth App,Homepage URL 填入
http://localhost:3000/
, Authorization callback URL 填入 Supabase 提供的Callback URL (for OAuth)
,点击 Register Application。 - 点击 Generate a new client secret,复制秘钥。
- 将 Client ID 和 Client Secret 分别填入,点击 Save。
修改 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
,此时我们已经完成了最基础的登录登出功能。
用户信息缓存
安装 zustand
pnpm install zustand
创建文件 /lib/store/user.ts
:
import { create } from "zustand";
import { User } from "@supabase/supabase-js";
interface UserState {
user: User | undefined;
setUser: (user: User | undefined) => void;
}
export const 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 数据表。
创建完成后进入 SQL Editor 填入下面两个 SQL,执行。
-- 创建 create_user_on_signup 函数,1.在 public.users 表中插入一条新的记录。2.更新 auth.users 表中的 raw_user_meta_data 字段。
CREATE FUNCTION create_user_on_signup() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.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 函数
CREATE TRIGGER create_user_on_signup after INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION create_user_on_signup();
- 进入 Authentication 将已经授权的用户删除
- 重新点击登录。此时 users 表已经同步新增了一条数据。
路由守卫
我们的一些页面是不希望未登录用户或者普通用户进行访问的,于是需要对页面进行拦截。
修改 middleware.ts
文件:
export async function middleware(request: NextRequest) {
...
const { data } = await supabase.auth.getSession();
if (!data.session || data.session.user.user_metadata.role !== 'admin') {
return NextResponse.redirect(new URL('/', request.url));
}
return response;
}
export const 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 页面中复制。文章来源:https://www.toymoban.com/news/detail-753444.html
退出重新登录,再次访问 /admin 页面,成功进入。文章来源地址https://www.toymoban.com/news/detail-753444.html
到了这里,关于Next.js使用Supabase实现Github登录的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!