Use MiniKit.walletAuth() to authenticate a user with Sign-In with Ethereum inside World App.
This is the recommended authentication flow for mini apps.
These examples assume MiniKit has already been initialized through
MiniKitProvider or MiniKit.install().
Availability
- Works natively in World App
- Can execute outside World App through Wagmi or a custom fallback when configured
Basic Usage
Generate the nonce on your backend. The nonce must be alphanumeric and at least 8 characters.
"use client";
import { MiniKit } from "@worldcoin/minikit-js";
import type {
CommandResultByVia,
MiniKitWalletAuthOptions,
WalletAuthResult,
} from "@worldcoin/minikit-js/commands";
export async function signInWithWallet() {
const response = await fetch("/api/nonce");
const { nonce } = await response.json();
const input = {
nonce,
statement: "Sign in to Example Mini App",
expirationTime: new Date(Date.now() + 1000 * 60 * 60),
// requestId: "optional-tracking-id",
// notBefore: new Date(),
} satisfies MiniKitWalletAuthOptions;
const result: CommandResultByVia<WalletAuthResult> =
await MiniKit.walletAuth(input);
if (result.executedWith === "fallback") {
return;
}
await fetch("/api/complete-siwe", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
payload: result.data,
nonce,
}),
});
}
Result
type WalletAuthResponse =
| {
executedWith: "minikit" | "wagmi";
data: {
address: string;
message: string;
signature: string;
};
}
| {
executedWith: "fallback";
data: unknown;
};
Fallback Behavior
executedWith: "wagmi" means the Wagmi integration handled the request
executedWith: "fallback" means your custom fallback function handled the request
Use executedWith to branch on the runtime path when needed.
Backend Verification
Always verify the returned SIWE payload on your backend.
import { cookies } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
import type { MiniAppWalletAuthSuccessPayload } from "@worldcoin/minikit-js/commands";
import { verifySiweMessage } from "@worldcoin/minikit-js/siwe";
type RequestBody = {
payload: MiniAppWalletAuthSuccessPayload;
nonce: string;
};
export async function POST(req: NextRequest) {
const { payload, nonce } = (await req.json()) as RequestBody;
if (nonce !== cookies().get("siwe")?.value) {
return NextResponse.json(
{ isValid: false, error: "Invalid nonce" },
{ status: 400 },
);
}
try {
// Optional: pass statement and requestId for additional server-side validation
const verification = await verifySiweMessage(
payload,
nonce,
// statement, — validates the statement matches what you sent
// requestId, — validates the request ID matches what you sent
// viemClient, — custom viem Client; defaults to a public Worldchain client
);
return NextResponse.json({
isValid: verification.isValid,
address: verification.siweMessageData.address,
});
} catch (error) {
return NextResponse.json(
{
isValid: false,
error: error instanceof Error ? error.message : "Unknown error",
},
{ status: 400 },
);
}
}
Options
| Field | Type | Required | Description |
|---|
nonce | string | Yes | Alphanumeric, at least 8 characters |
statement | string | No | Human-readable statement included in the SIWE message |
expirationTime | Date | No | When the SIWE message expires |
notBefore | Date | No | SIWE message is not valid before this time |
requestId | string | No | Arbitrary ID for correlating the request on your backend |
fallback | () => Promise<T> | No | Custom fallback for non-World-App environments |
Notes
- Use
MiniKit.user.walletAddress after successful auth if you need cached user state
- Use
MiniKit.getUserByAddress() or MiniKit.getUserByUsername() to resolve username and profile metadata
- Do not use World ID verification as a login substitute
Error Codes
| Code | Meaning |
|---|
malformed_request | The SIWE request payload is invalid |
user_rejected | The user rejected the signature request |
generic_error | Unexpected failure |
Preview