Skip to content

builders-garden/better-auth-siwf

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Better Auth – Sign In With Farcaster (SIWF)

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.

Installation

npm i better-auth-siwf

Server Setup

Add the SIWF plugin to your Better Auth configuration.

// auth.ts
import { betterAuth } from "better-auth";
import { type ResolveFarcasterUserResult, siwf } from "better-auth-siwf";

const auth = betterAuth({
	// ... your better-auth config
	plugins: [
		siwf({
			hostname: "app.example.com",
			allowUserToLink: false,
			// Optional: resolve the user data from neynar for example
			// see neynar docs: https://docs.neynar.com/reference/fetch-bulk-users
			resolveFarcasterUser: async ({
				fid,
			}): Promise<ResolveFarcasterUserResult | null> => {
				const data = await fetch(
					`https://api.neynar.com/v2/farcaster/user/bulk/?fids=${fid}`,
					{
						method: "GET",
						headers: {
							"x-api-key": process.env.NEYNAR_API_KEY,
							"Content-Type": "application/json",
						},
					},
				).then(async (data) => await data.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;
			},
		}),
	],
});

What the plugin does

  • Exposes POST /siwf/signin to signin a Farcaster Quick Auth JWT and establish a Better Auth session cookie.
  • Creates a user if one does not exist, associates it with a farcaster record.
  • Sets a secure session cookie with SameSite: "none" for Farcaster MiniApp compatibility.

Client Setup

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;

Usage

1) Obtain a Farcaster JWT token on the client

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 }

2) Verify and sign in

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.signInWithFarcaster({
  token: result.token,
  user: {
    ...ctx.user
    notificationDetails: ctx.client.notificationDetails
      ? [
          {
            ...ctx.client.notificationDetails,
            appFid: (await miniappSdk.context).client.clientFid
          }
        ]
      : [],
  }
});

// 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
        ? [
            {
              ...ctx.client.notificationDetails,
              appFid: (await miniappSdk.context).client.clientFid
            }
          ]
        : [],
    }
  });
  if (!data.success) {
    throw new Error("Failed to verify token");
  }
  console.log("Signed in", data.user);
};

Configuration Options

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 Auth mergeSchema.
  • resolveFarcasterUser (function): Resolve a Farcaster user via farcaster hubs.

Client plugin siwfClient has no options; it exposes the plugin namespace in the Better Auth client.

Database Schema

This plugin merges the following tables into your Better Auth schema.

farcaster

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

walletAddress

Field Type Notes
userId string References user.id (required)
address string The wallet address (required)
chainId number The chain ID (required)
isPrimary boolean Whether the address is primary (required)
createdAt date Required

Migrations

Use the Better Auth CLI to migrate or generate schema:

npx @better-auth/cli migrate
# or
npx @better-auth/cli generate

Alternatively, add the fields manually based on the tables above.

Security Notes

  • The server verifies Farcaster JWTs with the configured domain. Mismatched domains will fail.
  • Session cookies are set with secure: true, httpOnly: true, and sameSite: "none" for MiniApp compatibility. Serve over HTTPS.
  • The plugin @farcaster/quick-auth ensures the JWT sub (subject) matches the provided fid before issuing a session.

Troubleshooting

  • 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 domain configured in the plugin.

Acknowledgements

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published