Better Auth Worldcoin Minikit Plugin
The `better-auth-minikit` package provides a plugin for the `better-auth` authentication library, enabling seamless integration with Worldcoin Minikit for Sign-In With Ethereum (SIWE) based authentication flows. It is currently at version 1.0.10, indicating a stable release within its first major iteration. While a specific release cadence isn't stated, the `better-auth` ecosystem generally follows a responsive approach to updates. Key differentiators include its full SIWE implementation adapted for Minikit, robust multi-chain support for associating wallet addresses with specific chain IDs, automatic account linking for verified multiple wallet addresses, and compatibility with MiniApps via secure, partitioned session cookies (SameSite: "none"). It abstracts much of the SIWE complexity for `better-auth` users, focusing on secure server-side nonce generation and message verification, along with a client-side API for initiating the flow.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'minikit')
cause The `minikitClient` plugin was not correctly registered with `createAuthClient`.fixEnsure `minikitClient()` is included in the `plugins` array when calling `createAuthClient({ plugins: [minikitClient()] })`. -
Failed to get nonce: { message: 'Unauthorized', status: 401 }cause The server-side `betterAuth` instance with the `minikit` plugin is not correctly initialized or accessible, or there's an issue with your `better-auth` setup.fixVerify your server's `betterAuth` configuration, ensure the `minikit` plugin is added, and check that the endpoint receiving the `getNonce` request is correctly routed and uses the `betterAuth` instance. -
SIWE message verification failed: Error: Nonce mismatch
cause The nonce generated by the server does not match the nonce included in the SIWE message signed by the client, or the server's `getNonce` and `verifyMessage` implementations are inconsistent.fixDouble-check the `getNonce` implementation to ensure it returns a unique, random string and that this exact nonce is passed to `createSiweMessage` on the client and then used by `validateSiweMessage` on the server. -
SIWE message verification failed: Error: Domain mismatch
cause The domain specified in the SIWE message signed by the client does not match the `domain` configured in the server-side `minikit` plugin.fixEnsure the `domain` property provided to `minikit({ domain: '...' })` on the server is identical to the `domain` parameter used when creating the SIWE message on the client side.
Warnings
- gotcha When setting up the client, ensure `fetchOptions: { credentials: "include" }` is explicitly provided. This is crucial for correctly handling session cookies, especially in cross-origin or MiniApp environments where `SameSite: "none"` cookies are used.
- gotcha The `domain` parameter in the `minikit` server plugin must exactly match the `domain` used when creating the SIWE message on the client side. Mismatches will cause SIWE message verification to fail.
- gotcha The `verifyMessage` function on the server side requires robust handling of the SIWE message. Ensure you use a reliable library like `viem/siwe` to parse and validate the message, including checking `domain`, `nonce`, `address`, and `chainId` against expected values to prevent replay attacks and message tampering.
Install
-
npm install better-auth-minikit -
yarn add better-auth-minikit -
pnpm add better-auth-minikit
Imports
- minikit
const { minikit } = require('better-auth-minikit')import { minikit } from 'better-auth-minikit' - minikitClient
import { minikitClient } from 'better-auth-minikit'import { minikitClient } from 'better-auth-minikit/client' - AuthClientMinikit
type AuthClientMinikit = ClientPlugin<typeof minikitClient>
Quickstart
import { betterAuth } from "better-auth";
import { minikit } from "better-auth-minikit";
import { generateRandomString } from "better-auth/crypto";
import { parseSiweMessage, validateSiweMessage } from "viem/siwe";
import { createAuthClient } from "better-auth/react";
import { minikitClient } from "better-auth-minikit/client";
import { MiniKit } from '@worldcoin/minikit-js';
import { createSiweMessage } from 'viem/siwe'; // Helper to create SIWE messages
// --- Server Setup (Node.js/Edge Function) ---
export const auth = betterAuth({
plugins: [
minikit({
domain: "app.example.com", // Replace with your actual domain
getNonce: async () => {
return generateRandomString(32);
},
verifyMessage: async ({ message, signature, address, chainId }) => {
try {
const parsedMessage = await parseSiweMessage(message);
const valid = await validateSiweMessage({
message,
signature,
domain: "app.example.com",
nonce: parsedMessage.nonce,
address: address, // Ensure address is passed for robust validation
chainId: chainId // Ensure chainId is passed for robust validation
});
return valid;
} catch (e) {
console.error("SIWE message verification failed:", e);
return false;
}
}
})
]
});
// --- Client Setup (React Component or similar) ---
const client = createAuthClient({
plugins: [minikitClient()],
fetchOptions: {
credentials: "include", // Essential for session cookies in MiniApps
},
});
export const authClient = client;
// --- Client-side Usage Example (within an async function) ---
async function authenticateWithWorldcoin() {
const walletAddress = "0x..."; // User's actual wallet address from Minikit
const chainId = 1; // Example: Ethereum Mainnet
// 1. Get Nonce
const { data: nonceData, error: nonceError } = await authClient.minikit.getNonce({
walletAddress,
chainId
});
if (nonceError || !nonceData?.nonce) {
console.error("Failed to get nonce:", nonceError);
return;
}
const nonce = nonceData.nonce;
// 2. Sign Message (using Worldcoin Minikit SDK)
const message = createSiweMessage({
domain: window.location.host,
address: walletAddress,
statement: "Sign in to My App",
uri: window.location.origin,
version: "1",
chainId: chainId,
nonce: nonce,
});
let signature;
try {
// This part requires Worldcoin Minikit to be initialized and available
const { commandPayload } = await MiniKit.commands.signMessage({
message: message
});
signature = commandPayload;
} catch (signError) {
console.error("Failed to sign message with Minikit:", signError);
return;
}
// 3. Verify and Sign In
const { data: signInData, error: signInError } = await authClient.minikit.signInWithMinikit({
message,
signature,
walletAddress,
chainId,
user: {
username: "worldcoin-user",
profilePictureUrl: "https://example.com/default.png"
}
});
if (signInData?.success) {
console.log("Successfully signed in user:", signInData.user);
} else {
console.error("Sign-in failed:", signInError);
}
}
// Call the function to initiate authentication (example)
// authenticateWithWorldcoin();