SIWE Auth plugin
hey!
I'm trying to get a siwe-plugin working, but i'm pretty sure I'm doing something wrong here.
Once my plugin is initialized, I still can't access it via
signIn.siwe
2 Replies
index.ts, too long to paste:
https://privatebin.net/?9eb49b1afc9c5e5b#BmYfj1t5AJZVMyv62eiowufQcse6WVsK5cs7EUaTaiXA
client.ts:
made 2 actions which I can now call from my client comp.
I now get an user and session created after signing the nonce.
so is there anything missing from here, or it's good enough?
Thanks!
import type { BetterAuthClientPlugin } from "better-auth";
import { createSiweMessage } from "viem/siwe";
import type { Address, Hex } from "viem";
import type { siwePlugin } from "./index";
type SiwePlugin = typeof siwePlugin;
export type SiweClientPluginOptions = {
statement?: string;
domain?: string;
origin?: string;
chainId?: number;
};
// Extend the client plugin type to include our SIWE methods
declare module "better-auth" {
interface BetterAuthClientPlugin {
signIn?: {
siwe: (params: {
address: Address;
chainId: number;
signMessage: (message: string) => Promise<Hex>;
}) => Promise<{
user: {
id: string;
ethAddress: string;
name: string;
avatar: string;
email: string;
createdAt: Date;
updatedAt: Date;
};
session: {
token: string;
expiresAt: Date;
};
}>;
};
}
}
export const siweClientPlugin = (options?: SiweClientPluginOptions) => {
return {
id: "siwe",
$InferServerPlugin: {} as ReturnType<SiwePlugin>,
signIn: {
siwe: async ({
address,
chainId,
signMessage,
}: {
address: Address;
chainId: number;
signMessage: (message: string) => Promise<Hex>;
}) => {
// Get nonce from server
const nonceRes = await fetch("/api/auth/siwe/nonce", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
publicKey: address,
}),
});
if (!nonceRes.ok) {
throw new Error("Failed to get nonce");
}
const { nonce } = await nonceRes.json();
// Create SIWE message
const messageObj = createSiweMessage({
domain: options?.domain || window.location.host,
address,
statement:
options?.statement || "Sign in with Ethereum to authenticate.",
uri: options?.origin || window.location.origin,
version: "1",
chainId: options?.chainId || chainId,
nonce,
});
// Get signature from wallet
const signature = await signMessage(messageObj);
// Verify signature with server
const verifyRes = await fetch("/api/auth/siwe/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
message: messageObj,
signature,
publicKey: address,
}),
});
if (!verifyRes.ok) {
const error = await verifyRes.json();
throw new Error(error.message || "Failed to verify signature");
}
return verifyRes.json();
},
},
} satisfies BetterAuthClientPlugin;
};
import type { BetterAuthClientPlugin } from "better-auth";
import { createSiweMessage } from "viem/siwe";
import type { Address, Hex } from "viem";
import type { siwePlugin } from "./index";
type SiwePlugin = typeof siwePlugin;
export type SiweClientPluginOptions = {
statement?: string;
domain?: string;
origin?: string;
chainId?: number;
};
// Extend the client plugin type to include our SIWE methods
declare module "better-auth" {
interface BetterAuthClientPlugin {
signIn?: {
siwe: (params: {
address: Address;
chainId: number;
signMessage: (message: string) => Promise<Hex>;
}) => Promise<{
user: {
id: string;
ethAddress: string;
name: string;
avatar: string;
email: string;
createdAt: Date;
updatedAt: Date;
};
session: {
token: string;
expiresAt: Date;
};
}>;
};
}
}
export const siweClientPlugin = (options?: SiweClientPluginOptions) => {
return {
id: "siwe",
$InferServerPlugin: {} as ReturnType<SiwePlugin>,
signIn: {
siwe: async ({
address,
chainId,
signMessage,
}: {
address: Address;
chainId: number;
signMessage: (message: string) => Promise<Hex>;
}) => {
// Get nonce from server
const nonceRes = await fetch("/api/auth/siwe/nonce", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
publicKey: address,
}),
});
if (!nonceRes.ok) {
throw new Error("Failed to get nonce");
}
const { nonce } = await nonceRes.json();
// Create SIWE message
const messageObj = createSiweMessage({
domain: options?.domain || window.location.host,
address,
statement:
options?.statement || "Sign in with Ethereum to authenticate.",
uri: options?.origin || window.location.origin,
version: "1",
chainId: options?.chainId || chainId,
nonce,
});
// Get signature from wallet
const signature = await signMessage(messageObj);
// Verify signature with server
const verifyRes = await fetch("/api/auth/siwe/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
message: messageObj,
signature,
publicKey: address,
}),
});
if (!verifyRes.ok) {
const error = await verifyRes.json();
throw new Error(error.message || "Failed to verify signature");
}
return verifyRes.json();
},
},
} satisfies BetterAuthClientPlugin;
};
export async function getSiweNonce(publicKey: string) {
return await auth.api.getNonce({
body: {
publicKey,
},
});
}
export async function verifySiweSignature(
message: string,
signature: string,
publicKey: string
) {
return await auth.api.verify({
body: {
message,
signature,
publicKey,
},
});
}
export async function getSiweNonce(publicKey: string) {
return await auth.api.getNonce({
body: {
publicKey,
},
});
}
export async function verifySiweSignature(
message: string,
signature: string,
publicKey: string
) {
return await auth.api.verify({
body: {
message,
signature,
publicKey,
},
});
}
Hey, I'm not familar with SIWE, but maybe @bekacru could let you know if you're potentially missing anything.