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 🙏

© 2025 – Pkg Stats / Ryan Hefner

better-auth-farcaster-plugin

v4.0.19

Published

A community-made plug-in to authenticate Farcaster users with optional React hooks

Readme

Farcaster Plugin for Better-Auth

🗒️ Description

A community-made plugin that allows you to authenticate users via Farcaster. This plugin provides two authentication flows:

  1. Miniapp Flow - Uses Farcaster Quick Auth for JWT verification in Farcaster miniapp/frame context
  2. Core Flow - Uses Sign In With Farcaster (SIWF) for OAuth-like authentication on regular web pages

Features:

  • 🔐 Quick Auth sign-in for miniapps
  • 🌐 SIWF (OAuth-like) sign-in for web apps
  • 🔗 Link/unlink Farcaster accounts to existing users
  • ⚛️ Optional React hooks with automatic session management
  • 📊 Built-in rate limiting
  • 🎯 Full TypeScript support with typed errors
  • 🔄 Automatic session restoration and expiration handling

⚙️ Setup

Installation

npm install better-auth-farcaster-plugin
# or
pnpm add better-auth-farcaster-plugin

Peer Dependencies

Required:

  • better-auth (>=1.2.0)
  • zod (>=3.0.0)

Optional (for Miniapp flow):

  • @farcaster/quick-auth (>=0.0.8)

Optional (for Core/SIWF flow):

  • @farcaster/auth-client (>=0.1.0)
  • viem (>=2.0.0)

Optional (for React hooks):

  • react (>=17.0.0)

📱 Miniapp Flow (Quick Auth)

For Farcaster miniapps and frames where User context is available.

Server Setup

// auth.ts
import { betterAuth } from "better-auth";
import { farcasterAuth } from "better-auth-farcaster-plugin";
// Or explicitly: import { farcasterMiniappAuth } from "better-auth-farcaster-plugin/miniapp";

export const auth = betterAuth({
    plugins: [
        farcasterAuth({
            domain: process.env.BETTER_AUTH_URL || "https://example.com",
        }),
    ],
});

Client Setup

// auth-client.ts
import { createAuthClient } from "better-auth/react";
import { farcasterAuthClient } from "better-auth-farcaster-plugin/client";
// Or explicitly: import { farcasterMiniappClient } from "better-auth-farcaster-plugin/miniapp/client";

export const authClient = createAuthClient({
    baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL,
    plugins: [farcasterAuthClient()],
});

TypeScript Types

Better Auth's automatic type inference ($InferServerPlugin) does not work reliably with external npm packages. Use type casting for proper autocomplete:

// auth-client.ts
import { createAuthClient } from "better-auth/react";
import { farcasterMiniappClient, type FarcasterMiniappActions } from "better-auth-farcaster-plugin/miniapp/client";
import { farcasterCoreClient, type FarcasterCoreActions } from "better-auth-farcaster-plugin/core/client";

const client = createAuthClient({
    baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL,
    plugins: [farcasterMiniappClient(), farcasterCoreClient()],
});

// Export with proper types
export const authClient = client as typeof client & {
    farcasterMiniapp: FarcasterMiniappActions;
    farcaster: FarcasterCoreActions;
};

// Now you have full autocomplete!
authClient.farcasterMiniapp.signIn({ token: "..." });
authClient.farcaster.createChannel();

Alternative: Helper functions

import { getFarcasterMiniapp } from "better-auth-farcaster-plugin/miniapp/client";
import { getFarcasterCore } from "better-auth-farcaster-plugin/core/client";

const miniapp = getFarcasterMiniapp(authClient);  // Typed!
const core = getFarcasterCore(authClient);         // Typed!

React Hooks (Miniapp)

import { useFarcasterSignIn } from "better-auth-farcaster-plugin/react";
// Or: import { useFarcasterSignIn } from "better-auth-farcaster-plugin/miniapp/react";
import { authClient } from "./lib/auth-client";
import sdk from "@farcaster/frame-sdk";

function SignInButton() {
    const { signIn, signOut, isLoading, isAuthenticated, user } = useFarcasterSignIn({
        authClient,
        getToken: async () => {
            const result = await sdk.quickAuth.getToken();
            return result.token;
        },
        onSuccess: (response) => console.log("Signed in!", response.user),
    });

    if (isAuthenticated) {
        return (
            <div>
                <p>Welcome, {user?.name}!</p>
                <button onClick={signOut}>Sign Out</button>
            </div>
        );
    }

    return (
        <button onClick={signIn} disabled={isLoading}>
            Sign in with Farcaster
        </button>
    );
}

🌐 Core Flow (SIWF - OAuth-like)

For regular web apps where users authenticate via QR code or deeplink.

Install Additional Dependencies

pnpm add @farcaster/auth-client viem

Server Setup

// auth.ts
import { betterAuth } from "better-auth";
import { farcasterCoreAuth } from "better-auth-farcaster-plugin/core";

export const auth = betterAuth({
    plugins: [
        farcasterCoreAuth({
            domain: "example.com",
            siweUri: "https://example.com/login",
            // Optional: custom relay
            // relay: "https://relay.farcaster.xyz",
        }),
    ],
});

Client Setup

// auth-client.ts
import { createAuthClient } from "better-auth/react";
import { farcasterCoreClient } from "better-auth-farcaster-plugin/core/client";

export const authClient = createAuthClient({
    baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL,
    plugins: [farcasterCoreClient()],
});

React Hooks (Core)

import { useFarcasterSIWF } from "better-auth-farcaster-plugin/core/react";
import { authClient } from "./lib/auth-client";
import QRCode from "react-qr-code";

function SignInWithFarcaster() {
    const {
        createChannel,
        cancel,
        channelUrl,
        isLoading,
        isPolling,
        isAuthenticated,
        user,
    } = useFarcasterSIWF({
        authClient,
        onSuccess: (response) => console.log("Signed in!", response.user),
    });

    if (isAuthenticated) {
        return <p>Welcome, {user?.name}!</p>;
    }

    if (channelUrl) {
        return (
            <div>
                <QRCode value={channelUrl} />
                <p>Scan with Farcaster app or <a href={channelUrl}>click here</a></p>
                {isPolling && <p>Waiting for approval...</p>}
                <button onClick={cancel}>Cancel</button>
            </div>
        );
    }

    return (
        <button onClick={createChannel} disabled={isLoading}>
            Sign in with Farcaster
        </button>
    );
}

🎨 Pre-styled Button Components

Both flows include ready-to-use button components with Farcaster branding, automatic state management, and optional debug mode.

Miniapp Button

import { FarcasterButton } from "better-auth-farcaster-plugin/miniapp/react";
import { authClient } from "./lib/auth-client";
import sdk from "@farcaster/frame-sdk";

function App() {
    return (
        <FarcasterButton
            signInOptions={{
                authClient,
                getToken: async () => {
                    const result = await sdk.quickAuth.getToken();
                    return result.token;
                },
            }}
            debug={true} // Logs session data to console
        />
    );
}

Core Button (SIWF)

import { FarcasterCoreButton } from "better-auth-farcaster-plugin/core/react";
import { authClient } from "./lib/auth-client";
import QRCode from "react-qr-code"; // optional

function App() {
    return (
        <FarcasterCoreButton
            signInOptions={{ authClient }}
            debug={true}
            renderQRCode={(url) => <QRCode value={url} size={200} />}
            showLink={true} // Display link alongside QR code
        />
    );
}

Button Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | signInOptions | object | required | Hook options (authClient, callbacks) | | debug | boolean | false | Log session data to console when clicked | | className | string | - | Custom CSS class | | style | CSSProperties | - | Inline styles | | showAvatar | boolean | true | Show user avatar when authenticated | | signInText | string | "Sign in with Farcaster" | Sign in button text | | signOutText | string | "Sign out" | Sign out button text | | loadingText | string | "Loading..." | Loading state text |

Core-specific props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | waitingText | string | "Waiting for approval..." | Text during polling | | cancelText | string | "Cancel" | Cancel button text | | renderQRCode | (url: string) => ReactNode | - | Custom QR code renderer | | showLink | boolean | false | Show channel URL link alongside QR code |


📦 Import Paths

| Path | Description | |------|-------------| | better-auth-farcaster-plugin | Server plugin (defaults to miniapp) | | better-auth-farcaster-plugin/client | Client plugin (defaults to miniapp) | | better-auth-farcaster-plugin/react | React hooks (defaults to miniapp) | | better-auth-farcaster-plugin/miniapp | Miniapp server plugin | | better-auth-farcaster-plugin/miniapp/client | Miniapp client plugin | | better-auth-farcaster-plugin/miniapp/react | Miniapp React hooks | | better-auth-farcaster-plugin/core | Core SIWF server plugin | | better-auth-farcaster-plugin/core/client | Core SIWF client plugin | | better-auth-farcaster-plugin/core/react | Core SIWF React hooks |


⚛️ React Context Providers

Both flows provide context providers for easier state management:

Miniapp Provider

import { FarcasterProvider, useFarcaster } from "better-auth-farcaster-plugin/miniapp/react";

function App() {
    return (
        <FarcasterProvider
            signInOptions={{ authClient, getToken }}
            linkOptions={{ onLinkSuccess: (r) => console.log("Linked!") }}
        >
            <YourApp />
        </FarcasterProvider>
    );
}

function YourApp() {
    const { signIn, link } = useFarcaster();
    // ...
}

Core Provider

import { FarcasterCoreProvider, useFarcasterCore } from "better-auth-farcaster-plugin/core/react";

function App() {
    return (
        <FarcasterCoreProvider signInOptions={{ authClient }}>
            <YourApp />
        </FarcasterCoreProvider>
    );
}

function YourApp() {
    const { signIn, link } = useFarcasterCore();
    // signIn.createChannel(), signIn.channelUrl, etc.
}

📚 API Reference

Server Endpoints

Miniapp Flow (plugin ID: farcaster-miniapp):

| Endpoint | Method | Description | |----------|--------|-------------| | /farcaster-miniapp/sign-in | POST | Sign in with Quick Auth token | | /farcaster-miniapp/link | POST | Link Farcaster to existing account | | /farcaster-miniapp/unlink | POST | Unlink Farcaster from account | | /farcaster-miniapp/profile | GET | Get Farcaster profile |

Core Flow (plugin ID: farcaster):

| Endpoint | Method | Description | |----------|--------|-------------| | /farcaster/create-channel | POST | Create SIWF channel (returns QR URL) | | /farcaster/channel-status | POST | Poll channel status | | /farcaster/verify-signature | POST | Verify signature and create session | | /farcaster/link | POST | Link Farcaster to existing account | | /farcaster/unlink | POST | Unlink Farcaster from account | | /farcaster/profile | GET | Get Farcaster profile |

Error Codes

// Miniapp errors
type FarcasterAuthErrorCode =
    | 'INVALID_TOKEN' | 'SESSION_EXPIRED' | 'RATE_LIMITED'
    | 'NETWORK_ERROR' | 'TOKEN_FETCH_FAILED' | 'UNKNOWN';

// Core errors
type FarcasterCoreAuthErrorCode =
    | 'INVALID_SIGNATURE' | 'CHANNEL_EXPIRED' | 'CHANNEL_TIMEOUT'
    | 'SESSION_EXPIRED' | 'RATE_LIMITED' | 'NETWORK_ERROR'
    | 'POLLING_FAILED' | 'FID_MISMATCH' | 'UNKNOWN';

🔒 Rate Limiting

Miniapp Endpoints:

| Endpoint | Limit | |----------|-------| | /farcaster-miniapp/sign-in | 10/min | | /farcaster-miniapp/link | 5/min |

Core Endpoints:

| Endpoint | Limit | |----------|-------| | /farcaster/create-channel | 10/min | | /farcaster/verify-siwf | 10/min | | /farcaster/channel-status | 60/min | | /farcaster/link | 5/min |


🔄 Migration from v3.x to v4.x

⚠️ Breaking Changes in v4.0.0

The Miniapp plugin has been renamed to avoid conflicts when using both plugins together:

| v3.x | v4.x | |------|------| | Plugin ID: farcaster | Plugin ID: farcaster-miniapp | | Endpoints: /farcaster/* | Endpoints: /farcaster-miniapp/* | | Client: authClient.farcaster.* | Client: authClient.farcasterMiniapp.* |

Client Method Updates (Miniapp)

// v3.x
authClient.farcaster.signIn({ token })
authClient.farcaster.link({ token })
authClient.farcaster.unlink()
authClient.farcaster.profile()

// v4.x
authClient.farcasterMiniapp.signIn({ token })
authClient.farcasterMiniapp.link({ token })
authClient.farcasterMiniapp.unlink()
authClient.farcasterMiniapp.profile()

Using Both Plugins Together

You can now use both Miniapp and Core plugins without conflicts:

// Server
import { farcasterMiniappAuth } from "better-auth-farcaster-plugin/miniapp";
import { farcasterCoreAuth } from "better-auth-farcaster-plugin/core";

export const auth = betterAuth({
    plugins: [
        farcasterMiniappAuth({ domain: "example.com" }),
        farcasterCoreAuth({ domain: "example.com", siweUri: "https://example.com/login" }),
    ],
});

// Client
import { farcasterMiniappClient } from "better-auth-farcaster-plugin/miniapp/client";
import { farcasterCoreClient } from "better-auth-farcaster-plugin/core/client";

export const authClient = createAuthClient({
    plugins: [farcasterMiniappClient(), farcasterCoreClient()],
});

// Use both
authClient.farcasterMiniapp.signIn({ token });  // Miniapp flow
authClient.farcaster.createChannel();            // Core SIWF flow

Import Compatibility

Server imports still work (backward compatible):

import { farcasterAuth } from "better-auth-farcaster-plugin"; // Still works

However, client code needs to be updated to use farcasterMiniapp.* methods.


🔗 Dependencies

📄 License

ISC

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.