Skip to content
Auth

在 Astro 中使用 Supabase 认证

Learn how to configure Supabase Auth for Astro with server-side rendering.

快速入门 #

🌐 Quickstart

1
Create a new Supabase project

去 database.new 创建一个新的 Supabase 项目。

你的新数据库有一个用于存储用户的表。你可以通过在 SQL 编辑器 中运行一些 SQL 来看到这个表目前是空的。

SQL_EDITOR
select * from auth.users;
2
Create an Astro app

使用 npm create 命令创建一个新的 Astro 应用。

Terminal
npm create astro@latest my-app
cd my-app
3
Install Supabase libraries and Node adapter

安装 @supabase/supabase-js 客户端库,@supabase/ssr 用于服务器端认证,以及 @astrojs/node 适配器以启用服务器端渲染。

Terminal
npm install @supabase/supabase-js @supabase/ssr @astrojs/node
4
Configure Astro for SSR

更新你的 astro.config.mjs 来启用使用 Node 适配器的服务端渲染。

astro.config.mjs
import { defineConfig } from "astro/config";
import node from "@astrojs/node";
export default defineConfig({
output: "server",
adapter: node({
mode: "standalone",
}),
});
5
Declare Supabase Environment Variables

创建一个 .env.local 文件,并用你的 Supabase 连接变量填充:

.env.local
PUBLIC_SUPABASE_URL=your-project-url
PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_key

获取 API 详情 #

🌐 Get API details

要与数据库表中的数据进行交互,你可以使用封装了自动生成的数据 API 端点的客户端库,并使用来自项目 Connect 对话框的项目 URL 和密钥进行认证。

🌐 To interact with data in database tables, you use the client libraries that wrap the auto-generated Data API endpoints, authenticating using the Project URL and key from the project Connect dialog.

6
Create a Supabase client helper

创建一个工具文件来初始化带有 SSR 支持的 Supabase 客户端:

src/lib/supabase.ts
import { createServerClient, parseCookieHeader } from "@supabase/ssr";
import type { AstroCookies } from "astro";
const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL
const supabasePublishableKey = import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY
export function createClient({
request,
cookies,
}: {
request: Request;
cookies: AstroCookies;
}) {
return createServerClient(
supabaseUrl,
supabasePublishableKey,
{
cookies: {
getAll() {
return parseCookieHeader(
request.headers.get("Cookie") ?? ""
);
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookies.set(name, value, options)
);
},
},
}
);
}
7
Create authentication actions

在 src/actions/index.ts 创建一个新文件,用于定义服务器端的注册、登录和登出操作:

src/actions/index.ts
import { defineAction } from "astro:actions";
import { z } from "astro/zod";
import { createClient } from "../lib/supabase";
export const server = {
signUp: defineAction({
accept: "form",
input: z.object({
email: z.string().email(),
password: z.string().min(6),
}),
handler: async (input, context) => {
try {
const supabase = createClient({
request: context.request,
cookies: context.cookies,
});
const { error } = await supabase.auth.signUp({
email: input.email,
password: input.password,
options: {
emailRedirectTo: "http://localhost:4321/auth/callback",
},
});
if (error) {
return {
success: false,
message: error.message,
};
}
return {
success: true,
message: "Check your email to confirm your account",
};
} catch (err) {
return {
success: false,
message: "Unexpected error",
};
}
},
}),
signIn: defineAction({
accept: "form",
input: z.object({
email: z.string().email(),
password: z.string(),
}),
handler: async (input, context) => {
try {
const supabase = createClient({
request: context.request,
cookies: context.cookies,
});
const { error } = await supabase.auth.signInWithPassword({
email: input.email,
password: input.password,
});
if (error) {
return {
success: false,
message: error.message,
};
}
return {
success: true,
message: "Signed in successfully",
};
} catch (err) {
return {
success: false,
message: "Unexpected error",
};
}
},
}),
signOut: defineAction({
handler: async (_, context) => {
try {
const supabase = createClient({
request: context.request,
cookies: context.cookies,
});
await supabase.auth.signOut();
return {
success: true,
};
} catch (err) {
return {
success: false,
message: "Failed to sign out",
};
}
},
}),
};
8
Customize email template

在用户确认他们的邮箱之前,先更新 Supabase 邮件模板,将 token 哈希发送到你的回调 URL。

在你的 Supabase 项目控制面板 中:

  • 去 认证 > 邮件模板
  • 选择 确认注册 模板
  • 把 {{ .ConfirmationURL }} 改成 {{ .SiteURL }}/auth/callback?token_hash={{ .TokenHash }}&type=email。
  • 把你的网站链接改成 http://localhost:4321
Email\
{{ .SiteURL }}/auth/callback?token_hash={{ .TokenHash }}&type=email
9
Create an auth callback page

在 src/pages/auth/callback.astro 创建一个新文件来处理邮箱确认回调。从 URL 中提取令牌,并用 Supabase 验证它:

src/pages/auth/callback.astro
---
import { createClient } from "../../lib/supabase";
import type { EmailOtpType } from "@supabase/supabase-js";
const supabase = createClient({
request: Astro.request,
cookies: Astro.cookies,
});
const requestUrl = new URL(Astro.request.url);
const token_hash = requestUrl.searchParams.get('token_hash');
const type = requestUrl.searchParams.get('type') as EmailOtpType | null;
if (token_hash && type) {
const { error } = await supabase.auth.verifyOtp({
token_hash,
type,
});
if (!error) {
return Astro.redirect("/dashboard");
}
}
return Astro.redirect("/auth/signin");
---
<html>
<head>
<title>Email Confirmation</title>
</head>
<body>
<p>Confirming your email...</p>
</body>
</html>
10
Create a sign-up page

在 src/pages/auth/signup.astro 创建一个新文件,里面放一个注册表单。使用客户端事件监听器来处理表单提交:

src/pages/auth/signup.astro
---
import { createClient } from "../../lib/supabase";
const supabase = createClient({
request: Astro.request,
cookies: Astro.cookies,
});
const { data } = await supabase.auth.getUser();
if (data?.user) {
return Astro.redirect("/dashboard");
}
---
<html>
<head>
<title>Sign Up</title>
</head>
<body>
<h1>Sign Up</h1>
<div id="message"></div>
<form id="signup-form">
<div>
<label for="email">Email</label>
<input
id="email"
type="email"
name="email"
placeholder="your@email.com"
required
/>
</div>
<div>
<label for="password">Password</label>
<input
id="password"
type="password"
name="password"
placeholder="At least 6 characters"
required
/>
</div>
<button type="submit" id="signup-btn">Sign Up</button>
</form>
<p>
Already have an account? <a href="/auth/signin">Sign in</a>
</p>
</body>
</html>
<script>
import { actions } from "astro:actions";
const form = document.querySelector("#signup-form") as HTMLFormElement;
const btn = document.getElementById("signup-btn") as HTMLButtonElement;
const messageEl = document.getElementById("message") as HTMLDivElement;
form?.addEventListener("submit", async (e) => {
e.preventDefault();
btn.disabled = true;
btn.textContent = "Signing up...";
messageEl.textContent = "";
try {
const formData = new FormData(form);
const result = await actions.signUp(formData);
if (!result.data?.success) {
btn.disabled = false;
btn.textContent = "Sign Up";
messageEl.textContent = result.data?.message || "Sign up failed";
messageEl.style.color = "red";
return;
}
messageEl.textContent = result.data.message;
messageEl.style.color = "green";
btn.textContent = "Sign Up";
} catch (error) {
btn.disabled = false;
btn.textContent = "Sign Up";
messageEl.textContent = "An error occurred. Please try again.";
messageEl.style.color = "red";
console.error(error);
}
});
</script>
11
Create a sign-in page

在 src/pages/auth/signin.astro 创建一个新文件,里面放一个登录表单。使用客户端事件监听器来处理表单提交:

src/pages/auth/signin.astro
---
import { createClient } from "../../lib/supabase";
const supabase = createClient({
request: Astro.request,
cookies: Astro.cookies,
});
const { data } = await supabase.auth.getUser();
if (data?.user) {
return Astro.redirect("/dashboard");
}
---
<html>
<head>
<title>Sign In</title>
</head>
<body>
<h1>Sign In</h1>
<div id="message"></div>
<form id="signin-form">
<div>
<label for="email">Email</label>
<input
id="email"
type="email"
name="email"
placeholder="your@email.com"
required
/>
</div>
<div>
<label for="password">Password</label>
<input
id="password"
type="password"
name="password"
placeholder="Your password"
required
/>
</div>
<button type="submit" id="signin-btn">Sign In</button>
</form>
<p>
Don't have an account? <a href="/auth/signup">Sign up</a>
</p>
</body>
</html>
<script>
import { actions } from "astro:actions";
const form = document.querySelector("#signin-form") as HTMLFormElement;
const btn = document.getElementById("signin-btn") as HTMLButtonElement;
const messageEl = document.getElementById("message") as HTMLDivElement;
form?.addEventListener("submit", async (e) => {
e.preventDefault();
btn.disabled = true;
btn.textContent = "Signing in...";
messageEl.textContent = "";
try {
const formData = new FormData(form);
const result = await actions.signIn(formData);
if (!result.data?.success) {
btn.disabled = false;
btn.textContent = "Sign In";
messageEl.textContent = result.data?.message || "Sign in failed";
messageEl.style.color = "red";
return;
}
// Redirect to dashboard on successful sign in
window.location.href = "/dashboard";
} catch (error) {
btn.disabled = false;
btn.textContent = "Sign In";
messageEl.textContent = "An error occurred. Please try again.";
messageEl.style.color = "red";
console.error(error);
}
});
</script>
12
Create a dashboard page

在 src/pages/dashboard.astro 创建一个新文件来显示已认证用户的信息。对登出按钮使用客户端事件监听器:

src/pages/dashboard.astro
---
import { createClient } from "../lib/supabase";
const supabase = createClient({
request: Astro.request,
cookies: Astro.cookies,
});
const { data } = await supabase.auth.getUser();
const user = data?.user;
if (!user) {
return Astro.redirect("/auth/signin");
}
---
<html>
<head>
<title>Dashboard</title>
</head>
<body>
<h1>Welcome!</h1>
<p>Email: {user.email}</p>
<p>User ID: {user.id}</p>
<button id="signout-btn">Sign Out</button>
</body>
</html>
<script>
import { actions } from "astro:actions";
const btn = document.getElementById("signout-btn") as HTMLButtonElement;
btn?.addEventListener("click", async (e) => {
e.preventDefault();
btn.disabled = true;
btn.textContent = "Signing out...";
try {
const result = await actions.signOut();
if (!result.data?.success) {
btn.disabled = false;
btn.textContent = "Sign Out";
alert("Failed to sign out");
return;
}
// Redirect to signin page
window.location.href = "/auth/signin";
} catch (error) {
btn.disabled = false;
btn.textContent = "Sign Out";
console.error(error);
}
});
</script>
13
Start the app

启动开发服务器,然后访问 http://localhost:4321/auth/signup 测试身份验证。

Terminal
npm run dev

了解更多 #

🌐 Learn more