Sign In With Farcaster (SIWF) Plugin for Better Auth

1.0.28 · active · verified Wed Apr 22

The `better-auth-siwf` package provides a plugin for the `better-auth` framework, enabling users to authenticate via Farcaster identities. It mirrors the developer experience of the official Sign In With Ethereum (SIWE) plugin, adapting the authentication flows and schema for Farcaster. This plugin handles the server-side verification of Farcaster Quick Auth JWTs, establishes Better Auth session cookies, and can optionally resolve enriched Farcaster user data via a provided callback (e.g., from Neynar API). The current stable version is 1.0.28. While a specific release cadence isn't stated, the 1.0.x versioning suggests a focus on stability with incremental improvements. Its key differentiator is its seamless integration with the `better-auth` ecosystem, offering a standardized approach to Farcaster authentication within that framework.

Common errors

Warnings

Install

Imports

Quickstart

Demonstrates setting up the `better-auth-siwf` plugin on both the server and client, including Farcaster JWT acquisition and verification to establish a Better Auth session. It also shows optional Farcaster user data resolution via Neynar.

import { betterAuth } from "better-auth";
import { type ResolveFarcasterUserResult, siwf } from "better-auth-siwf";
import { createAuthClient } from "better-auth/react";
import { siwfClient, type SIWFClientType } from "better-auth-siwf";

// --- Server-side configuration (e.g., auth.ts) ---
const NEYNAR_API_KEY = process.env.NEYNAR_API_KEY ?? ''; // Replace with actual env var or error handling

const auth = betterAuth({
  // ... your better-auth config
  plugins: [
    siwf({
      hostname: "app.example.com", // Crucial: must match the domain used in Farcaster Quick Auth
      allowUserToLink: false,
      resolveFarcasterUser: async ({
        fid,
      }): Promise<ResolveFarcasterUserResult | null> => {
        if (!NEYNAR_API_KEY) {
            console.warn("Neynar API key not set. Farcaster user details will not be resolved.");
            return null;
        }
        try {
          const response = await fetch(
            `https://api.neynar.com/v2/farcaster/user/bulk/?fids=${fid}`,
            {
              method: "GET",
              headers: {
                "x-api-key": NEYNAR_API_KEY,
                "Content-Type": "application/json",
              },
            }
          );
          const data = await response.json();

          if (!data || data.users.length === 0) {
            return null;
          }

          const user = data.users[0];
          return {
            fid,
            username: user.username,
            displayName: user.display_name,
            avatarUrl: user.pfp_url,
            custodyAddress: user.custody_address,
            verifiedAddresses: {
              primary: {
                ethAddress: user.verified_addresses.primary?.eth_address ?? undefined,
                solAddress: user.verified_addresses.primary?.sol_address ?? undefined,
              },
              ethAddresses: user.verified_addresses?.eth_addresses ?? undefined,
              solAddresses: user.verified_addresses?.sol_addresses ?? undefined,
            },
          } satisfies ResolveFarcasterUserResult;
        } catch (error) {
          console.error("Error resolving Farcaster user with Neynar:", error);
          return null;
        }
      },
    }),
  ],
});

// --- Client-side configuration (e.g., auth-client.ts) ---
// Assume miniappSdk is available in a Farcaster MiniApp environment
declare const miniappSdk: { quickAuth: { getToken: () => Promise<{ token: string }> }, context: Promise<{ user: any; client: { clientFid: string; notificationDetails?: any } }> };

const client = createAuthClient({
  plugins: [siwfClient()],
  fetchOptions: {
    credentials: "include", // Required for session cookies
  },
});

export const authClient = client as typeof client & SIWFClientType;

// --- Client-side usage (e.g., in a React component) ---
async function signIn() {
  try {
    // 1) Obtain a Farcaster JWT token on the client
    const result = await miniappSdk.quickAuth.getToken(); // result: { token: string }

    // 2) Verify and sign in with the Better Auth server
    const ctx = await miniappSdk.context;
    const { data } = await authClient.signInWithFarcaster({
      token: result.token,
      user: {
        ...ctx.user,
        notificationDetails: ctx.client.notificationDetails
          ? [
              {
                ...ctx.client.notificationDetails,
                appFid: (await miniappSdk.context).client.clientFid
              }
            ]
          : [],
      }
    });

    if (data.success) {
      console.log("Signed in successfully! User:", data.user);
      // Redirect or update UI
    } else {
      console.error("Farcaster sign-in failed:", data.error);
    }
  } catch (error) {
    console.error("An error occurred during Farcaster sign-in:", error);
  }
}

signIn(); // Call this function from your client-side logic

view raw JSON →