Authenticate users via Farcaster using Better Auth. This plugin mirrors the developer experience of the official SIWE plugin while adapting flows and schema to Farcaster identities.
- Server plugin:
siwf - Client plugin:
siwfClient - REST endpoints:
POST /siwf/verify
References: see the official SIWE plugin docs for structure and expectations and an earlier community attempt for Farcaster-specific ideas: SIWE Plugin Docs, it's also an expansion of this other plugin Farcaster Auth Plugin.
npm i better-auth-siwfAdd the SIWF plugin to your Better Auth configuration.
// auth.ts
import { betterAuth } from "better-auth";
import { generateRandomString } from "better-auth/crypto";
import { siwf } from "better-auth-siwf";
export const auth = betterAuth({
// database: { ... } // your DB config
plugins: [
siwf({
// must match the domain used when verifying the Farcaster JWT
domain: "app.example.com",
// (optional) whether the user is allowed to link their Farcaster account to their Better Auth account
allowUserToLink: true,
})
]
});- Exposes
POST /siwf/verifyto verify a Farcaster Quick Auth JWT and establish a Better Auth session cookie. - Creates a
userif one does not exist, associates it with afarcasterrecord. - Sets a secure session cookie with
SameSite: "none"for Farcaster MiniApp compatibility.
Add the client plugin so the Better Auth client exposes SIWF endpoints.
// auth-client.ts
import { createAuthClient } from "better-auth/react";
import { siwfClient, type SIWFClientType } from "better-auth-siwf";
const client = createAuthClient({
plugins: [siwfClient()],
fetchOptions: {
credentials: "include", // Required for session cookies
},
});
// Type the client to include custom farcaster methods
export const authClient = client as typeof client & SIWFClientType;Use Farcaster Quick Auth (within a Farcaster MiniApp) to obtain a signed JWT for your domain. Ensure the domain used here matches the server plugin domain.
const result = await miniappSdk.quickAuth.getToken(); // result: { token: string }Send the token and user details to the Better Auth server. On success, the Better Auth session cookie is set.
const ctx = await miniappSdk.context;
const { data } = await authClient.siwf.verifyToken({
token: result.token,
user: {
...ctx.user
notificationDetails: ctx.client.notificationDetails ?? undefined,
}
});
// data.success === true
// data.user -> { id, fid, name, image }All together:
import { sdk as miniappSdk } from "@farcaster/miniapp-sdk";
import { authClient } from "@/lib/auth-client";
const farcasterSignIn = async () => {
const isInMiniapp = await miniappSdk.isInMiniApp();
if (!isInMiniapp) {
return;
}
const ctx = await miniappSdk.context;
// 1. Obtain a Farcaster JWT token on the client
const result = await miniappSdk.quickAuth.getToken();
if (!result || !result.token) {
throw new Error("Failed to get token");
}
// 2. Verify and sign in with the Better Auth server
const { data } = await authClient.siwf.verifyToken({
token: result.token,
user: {
...ctx.user
notificationDetails: ctx.client.notificationDetails ?? undefined,
}
});
if (!data.success) {
throw new Error("Failed to verify token");
}
console.log("Signed in", data.user);
};Server options accepted by siwf:
domain(string, required): Domain expected in the Farcaster JWT. Must match exactly.schema(optional): Extend or override the default plugin schema via Better AuthmergeSchema.
Client plugin siwfClient has no options; it exposes the plugin namespace in the Better Auth client.
This plugin merges the following tables into your Better Auth schema.
| Field | Type | Notes |
|---|---|---|
| userId | string | References user.id (required) |
| fid | number | Unique Farcaster ID (required) |
| username | string | Optional |
| displayName | string | Optional |
| avatarUrl | string | Optional |
| notificationDetails | json | Optional (MiniApp notification array) |
| createdAt | date | Required |
| updatedAt | date | Required |
Use the Better Auth CLI to migrate or generate schema:
npx @better-auth/cli migrate
# or
npx @better-auth/cli generateAlternatively, add the fields manually based on the tables above.
- The server verifies Farcaster JWTs with the configured
domain. Mismatched domains will fail. - Session cookies are set with
secure: true,httpOnly: true, andsameSite: "none"for MiniApp compatibility. Serve over HTTPS. - The plugin @farcaster/quick-auth ensures the JWT
sub(subject) matches the providedfidbefore issuing a session.
- 401 "Invalid Farcaster user": The JWT subject must equal the provided
fid. - No session cookie set: In embedded contexts (MiniApps), ensure third-party cookies are allowed and your server uses HTTPS with
SameSite: none. - Domain mismatch: The JWT must be issued for the same
domainconfigured in the plugin.
- Structure and schema patterns inspired by the official Better Auth SIWE plugin: SIWE Plugin Docs
- Community exploration for Farcaster auth flows: Community Farcaster Auth Plugin