{"id":16770,"library":"better-auth-siwf","title":"Sign In With Farcaster (SIWF) Plugin for Better Auth","description":"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.","status":"active","version":"1.0.28","language":"javascript","source_language":"en","source_url":"https://github.com/builders-garden/better-auth-siwf","tags":["javascript","better-auth","farcaster","siwf","plugin","typescript"],"install":[{"cmd":"npm install better-auth-siwf","lang":"bash","label":"npm"},{"cmd":"yarn add better-auth-siwf","lang":"bash","label":"yarn"},{"cmd":"pnpm add better-auth-siwf","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Peer dependency required for the core authentication framework this plugin extends.","package":"better-auth","optional":false}],"imports":[{"note":"The server-side plugin function used to configure Better Auth with Farcaster authentication. This package is ESM-first and primarily consumed in TypeScript environments.","wrong":"const { siwf } = require('better-auth-siwf')","symbol":"siwf","correct":"import { siwf } from 'better-auth-siwf'"},{"note":"The client-side plugin function used with `createAuthClient` from `better-auth/react` to expose SIWF-specific methods on the client.","wrong":"const { siwfClient } = require('better-auth-siwf')","symbol":"siwfClient","correct":"import { siwfClient } from 'better-auth-siwf'"},{"note":"TypeScript type import for the return value of the optional `resolveFarcasterUser` callback. Use the `type` keyword for type-only imports to ensure proper tree-shaking and compilation.","wrong":"import { ResolveFarcasterUserResult } from 'better-auth-siwf'","symbol":"ResolveFarcasterUserResult","correct":"import { type ResolveFarcasterUserResult } from 'better-auth-siwf'"},{"note":"TypeScript type used for augmenting the `createAuthClient` return type, providing type inference for SIWF-specific methods on the client-side `authClient` instance.","wrong":"import { SIWFClientType } from 'better-auth-siwf'","symbol":"SIWFClientType","correct":"import { type SIWFClientType } from 'better-auth-siwf'"}],"quickstart":{"code":"import { betterAuth } from \"better-auth\";\nimport { type ResolveFarcasterUserResult, siwf } from \"better-auth-siwf\";\nimport { createAuthClient } from \"better-auth/react\";\nimport { siwfClient, type SIWFClientType } from \"better-auth-siwf\";\n\n// --- Server-side configuration (e.g., auth.ts) ---\nconst NEYNAR_API_KEY = process.env.NEYNAR_API_KEY ?? ''; // Replace with actual env var or error handling\n\nconst auth = betterAuth({\n  // ... your better-auth config\n  plugins: [\n    siwf({\n      hostname: \"app.example.com\", // Crucial: must match the domain used in Farcaster Quick Auth\n      allowUserToLink: false,\n      resolveFarcasterUser: async ({\n        fid,\n      }): Promise<ResolveFarcasterUserResult | null> => {\n        if (!NEYNAR_API_KEY) {\n            console.warn(\"Neynar API key not set. Farcaster user details will not be resolved.\");\n            return null;\n        }\n        try {\n          const response = await fetch(\n            `https://api.neynar.com/v2/farcaster/user/bulk/?fids=${fid}`,\n            {\n              method: \"GET\",\n              headers: {\n                \"x-api-key\": NEYNAR_API_KEY,\n                \"Content-Type\": \"application/json\",\n              },\n            }\n          );\n          const data = await response.json();\n\n          if (!data || data.users.length === 0) {\n            return null;\n          }\n\n          const user = data.users[0];\n          return {\n            fid,\n            username: user.username,\n            displayName: user.display_name,\n            avatarUrl: user.pfp_url,\n            custodyAddress: user.custody_address,\n            verifiedAddresses: {\n              primary: {\n                ethAddress: user.verified_addresses.primary?.eth_address ?? undefined,\n                solAddress: user.verified_addresses.primary?.sol_address ?? undefined,\n              },\n              ethAddresses: user.verified_addresses?.eth_addresses ?? undefined,\n              solAddresses: user.verified_addresses?.sol_addresses ?? undefined,\n            },\n          } satisfies ResolveFarcasterUserResult;\n        } catch (error) {\n          console.error(\"Error resolving Farcaster user with Neynar:\", error);\n          return null;\n        }\n      },\n    }),\n  ],\n});\n\n// --- Client-side configuration (e.g., auth-client.ts) ---\n// Assume miniappSdk is available in a Farcaster MiniApp environment\ndeclare const miniappSdk: { quickAuth: { getToken: () => Promise<{ token: string }> }, context: Promise<{ user: any; client: { clientFid: string; notificationDetails?: any } }> };\n\nconst client = createAuthClient({\n  plugins: [siwfClient()],\n  fetchOptions: {\n    credentials: \"include\", // Required for session cookies\n  },\n});\n\nexport const authClient = client as typeof client & SIWFClientType;\n\n// --- Client-side usage (e.g., in a React component) ---\nasync function signIn() {\n  try {\n    // 1) Obtain a Farcaster JWT token on the client\n    const result = await miniappSdk.quickAuth.getToken(); // result: { token: string }\n\n    // 2) Verify and sign in with the Better Auth server\n    const ctx = await miniappSdk.context;\n    const { data } = await authClient.signInWithFarcaster({\n      token: result.token,\n      user: {\n        ...ctx.user,\n        notificationDetails: ctx.client.notificationDetails\n          ? [\n              {\n                ...ctx.client.notificationDetails,\n                appFid: (await miniappSdk.context).client.clientFid\n              }\n            ]\n          : [],\n      }\n    });\n\n    if (data.success) {\n      console.log(\"Signed in successfully! User:\", data.user);\n      // Redirect or update UI\n    } else {\n      console.error(\"Farcaster sign-in failed:\", data.error);\n    }\n  } catch (error) {\n    console.error(\"An error occurred during Farcaster sign-in:\", error);\n  }\n}\n\nsignIn(); // Call this function from your client-side logic","lang":"typescript","description":"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."},"warnings":[{"fix":"Ensure `siwf({ hostname: 'your.domain.com' })` on the server and `miniappSdk.quickAuth.getToken({ domain: 'your.domain.com' })` on the client use identical domain names.","message":"The `hostname` configured in the server-side `siwf` plugin MUST exactly match the `domain` used when obtaining the JWT via Farcaster Quick Auth on the client. A mismatch will cause JWT verification to fail.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Add `fetchOptions: { credentials: 'include' }` to your `createAuthClient` call, as shown in the quickstart example.","message":"The client-side `createAuthClient` configuration requires `fetchOptions: { credentials: 'include' }` to ensure that the session cookie set by the `better-auth-siwf` plugin on the server is correctly sent and received by the browser.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Always use `import { type SIWFClientType } from 'better-auth-siwf'` for type-only imports.","message":"When augmenting the `authClient` type with `SIWFClientType`, ensure you use `import { type SIWFClientType } from 'better-auth-siwf'` with the `type` keyword. Omitting `type` can lead to bundling issues or unexpected behavior in some environments.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Implement the `resolveFarcasterUser` callback and integrate with a Farcaster API (like Neynar) to fetch and return detailed user information.","message":"The `resolveFarcasterUser` callback is optional but highly recommended for enriching the user profile with details like username, display name, and avatar URL from a Farcaster-compatible API (e.g., Neynar). Without it, only basic FID and address information will be available.","severity":"gotcha","affected_versions":">=1.0.0"}],"env_vars":null,"last_verified":"2026-04-22T00:00:00.000Z","next_check":"2026-07-21T00:00:00.000Z","problems":[{"fix":"Ensure `createAuthClient({ plugins: [siwfClient()] })` is configured and that `export const authClient = client as typeof client & SIWFClientType;` is used.","cause":"The `siwfClient()` plugin was not added to `createAuthClient`, or the client instance was not correctly type-augmented with `SIWFClientType`.","error":"TypeError: authClient.signInWithFarcaster is not a function"},{"fix":"Verify that `siwf({ hostname: 'your.domain.com' })` on the server and the domain parameter used when obtaining the Farcaster JWT on the client (e.g., `miniappSdk.quickAuth.getToken({ domain: 'your.domain.com' })`) are identical.","cause":"The `hostname` configured in the server-side `siwf` plugin does not match the `domain` provided during Farcaster Quick Auth on the client.","error":"Farcaster JWT verification failed: Invalid domain"},{"fix":"Set the `NEYNAR_API_KEY` (or equivalent) environment variable on your server where `better-auth` is running, or ensure the key is correctly passed to your API calls within `resolveFarcasterUser`.","cause":"The `resolveFarcasterUser` callback, if implemented to use an external API like Neynar, requires an API key that was not provided or was incorrect.","error":"Error: Missing NEYNAR_API_KEY environment variable (or similar API key error from `resolveFarcasterUser`)"}],"ecosystem":"npm","meta_description":null}