feat: sveltekit cookie helper plugin (#3049)

* fix(email-verification): improve email verification logic to check session and user email consistency (#3042)

* docs(passkey): Fixed signIn passkey props (#3014)

callbackURL doesn't exist.

* feat: svelte kit cookie helper

* lint

* docs and clean up

* clean up

* lint

* clean up

* clean up

* sync cookies

* lint

* pruning

* pruning and lint

* update

* update

* update

---------

Co-authored-by: Bereket Engida <86073083+Bekacru@users.noreply.github.com>
Co-authored-by: Maxwell <145994855+ping-maxwell@users.noreply.github.com>
This commit is contained in:
KinfeMichael Tariku
2025-06-20 13:47:26 -07:00
committed by GitHub
parent be3face70c
commit 4e38645b44
5 changed files with 91 additions and 8 deletions

View File

@@ -28,7 +28,6 @@ import { useAtom } from "jotai";
import { optionsAtom } from "./store";
import { useTheme } from "next-themes";
import { ScrollArea } from "../ui/scroll-area";
import styles from "./builder.module.css";
const frameworks = [
{
title: "Next.js",

View File

@@ -18,6 +18,27 @@ export async function handle({ event, resolve }) {
}
```
### Server Action Cookies
To ensure cookies are properly set when you call functions like `signInEmail` or `signUpEmail` in a server action, you should use the `sveltekitCookies` plugin. This plugin will automatically handle setting cookies for you in SvelteKit.
You need to add it as a plugin to your Better Auth instance.
<Callout>
The `getRequestEvent` function is available in SvelteKit `2.2.0` and later. Make sure you are using a compatible version.
</Callout>
```ts title="lib/auth.ts"
import { BetterAuth } from "better-auth";
import { sveltekitCookies } from "better-auth/svelte-kit";
export const auth = betterAuth({
// ... your config
plugins: [sveltekitCookies()]
})
```
## Create a client
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.

View File

@@ -117,7 +117,9 @@ Signin method accepts:
`autoFill`: Browser autofill, a.k.a. Conditional UI. [read more](https://simplewebauthn.dev/docs/packages/browser#browser-autofill-aka-conditional-ui)
`callbackURL`: The URL to redirect to after the user has signed in. (optional)
`email`: The email of the user to sign in.
`fetchOptions`: Fetch options to pass to the fetch request.
```ts
const data = await authClient.signIn.passkey();

View File

@@ -4,7 +4,6 @@ import { APIError } from "better-call";
import { getSessionFromCtx } from "./session";
import { setSessionCookie } from "../../cookies";
import type { GenericEndpointContext, User } from "../../types";
import { BASE_ERROR_CODES } from "../../error/codes";
import { jwtVerify, type JWTPayload, type JWTVerifyResult } from "jose";
import { signJWT } from "../../crypto/jwt";
import { originCheck } from "../middlewares";
@@ -155,13 +154,32 @@ export const sendVerificationEmail = createAuthEndpoint(
});
}
const { email } = ctx.body;
const user = await ctx.context.internalAdapter.findUserByEmail(email);
if (!user) {
throw new APIError("BAD_REQUEST", {
message: BASE_ERROR_CODES.USER_NOT_FOUND,
const session = await getSessionFromCtx(ctx);
if (!session) {
const user = await ctx.context.internalAdapter.findUserByEmail(email);
if (!user) {
//we're returning true to avoid leaking information about the user
return ctx.json({
status: true,
});
}
await sendVerificationEmailFn(ctx, user.user);
return ctx.json({
status: true,
});
}
await sendVerificationEmailFn(ctx, user.user);
if (session?.user.emailVerified) {
throw new APIError("BAD_REQUEST", {
message:
"You can only send a verification email to an unverified email",
});
}
if (session?.user.email !== email) {
throw new APIError("BAD_REQUEST", {
message: "You can only send a verification email to your own email",
});
}
await sendVerificationEmailFn(ctx, session.user);
return ctx.json({
status: true,
});

View File

@@ -1,4 +1,7 @@
import type { BetterAuthOptions } from "../types";
import type { BetterAuthPlugin } from "../types";
import { createAuthMiddleware } from "../api";
import { parseSetCookieHeader } from "../cookies";
export const toSvelteKitHandler = (auth: {
handler: (request: Request) => any;
@@ -49,3 +52,43 @@ export function isAuthPath(url: string, options: BetterAuthOptions) {
return false;
return true;
}
export const sveltekitCookies = () => {
return {
id: "sveltekit-cookies",
hooks: {
after: [
{
matcher() {
return true;
},
handler: createAuthMiddleware(async (ctx) => {
const returned = ctx.context.responseHeaders;
if ("_flag" in ctx && ctx._flag === "router") {
return;
}
if (returned instanceof Headers) {
const setCookies = returned?.get("set-cookie");
if (!setCookies) return;
// @ts-expect-error
const { getRequestEvent } = await import("$app/server");
const event = await getRequestEvent();
if (!event) return;
const parsed = parseSetCookieHeader(setCookies);
for (const [name, { value, ...ops }] of parsed) {
event.cookies.set(name, decodeURIComponent(value), {
sameSite: ops.samesite,
path: ops.path || "/",
expires: ops.expires,
secure: ops.secure,
httpOnly: ops.httponly,
domain: ops.domain,
maxAge: ops["max-age"],
});
}
}
}),
},
],
},
} satisfies BetterAuthPlugin;
};