npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

better-auth-siwf

v1.0.28

Published

better-auth plugin for Sign In With Farcaster

Readme

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