mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-11 04:19:31 +00:00
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:
committed by
GitHub
parent
be3face70c
commit
4e38645b44
@@ -28,7 +28,6 @@ import { useAtom } from "jotai";
|
|||||||
import { optionsAtom } from "./store";
|
import { optionsAtom } from "./store";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { ScrollArea } from "../ui/scroll-area";
|
import { ScrollArea } from "../ui/scroll-area";
|
||||||
import styles from "./builder.module.css";
|
|
||||||
const frameworks = [
|
const frameworks = [
|
||||||
{
|
{
|
||||||
title: "Next.js",
|
title: "Next.js",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.
|
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.
|
||||||
|
|||||||
@@ -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)
|
`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
|
```ts
|
||||||
const data = await authClient.signIn.passkey();
|
const data = await authClient.signIn.passkey();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { APIError } from "better-call";
|
|||||||
import { getSessionFromCtx } from "./session";
|
import { getSessionFromCtx } from "./session";
|
||||||
import { setSessionCookie } from "../../cookies";
|
import { setSessionCookie } from "../../cookies";
|
||||||
import type { GenericEndpointContext, User } from "../../types";
|
import type { GenericEndpointContext, User } from "../../types";
|
||||||
import { BASE_ERROR_CODES } from "../../error/codes";
|
|
||||||
import { jwtVerify, type JWTPayload, type JWTVerifyResult } from "jose";
|
import { jwtVerify, type JWTPayload, type JWTVerifyResult } from "jose";
|
||||||
import { signJWT } from "../../crypto/jwt";
|
import { signJWT } from "../../crypto/jwt";
|
||||||
import { originCheck } from "../middlewares";
|
import { originCheck } from "../middlewares";
|
||||||
@@ -155,16 +154,35 @@ export const sendVerificationEmail = createAuthEndpoint(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { email } = ctx.body;
|
const { email } = ctx.body;
|
||||||
|
const session = await getSessionFromCtx(ctx);
|
||||||
|
if (!session) {
|
||||||
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new APIError("BAD_REQUEST", {
|
//we're returning true to avoid leaking information about the user
|
||||||
message: BASE_ERROR_CODES.USER_NOT_FOUND,
|
return ctx.json({
|
||||||
|
status: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await sendVerificationEmailFn(ctx, user.user);
|
await sendVerificationEmailFn(ctx, user.user);
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
status: true,
|
status: true,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import type { BetterAuthOptions } from "../types";
|
import type { BetterAuthOptions } from "../types";
|
||||||
|
import type { BetterAuthPlugin } from "../types";
|
||||||
|
import { createAuthMiddleware } from "../api";
|
||||||
|
import { parseSetCookieHeader } from "../cookies";
|
||||||
|
|
||||||
export const toSvelteKitHandler = (auth: {
|
export const toSvelteKitHandler = (auth: {
|
||||||
handler: (request: Request) => any;
|
handler: (request: Request) => any;
|
||||||
@@ -49,3 +52,43 @@ export function isAuthPath(url: string, options: BetterAuthOptions) {
|
|||||||
return false;
|
return false;
|
||||||
return true;
|
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;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user