Trader Launchpad
Trader Launchpad10mo ago

Thoughts on how to integrate t3 app, connectkit web3 auth, nextjs middleware, and trpc

I am prototyping an application using t3 app with trpc, connectkit web3 auth. I am wanting to use nextjs middleware to protect routes server side. As part of the connectkit auth flow, I have my application wrapped in a ClientProvider:
// layout.tsx
<body
className={`font-sans ${inter.variable} flex min-h-screen flex-1 items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white`}
>
<TRPCReactProvider headers={headers()}>
<ClientProvider>{children}</ClientProvider>
</TRPCReactProvider>
</body>
// layout.tsx
<body
className={`font-sans ${inter.variable} flex min-h-screen flex-1 items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white`}
>
<TRPCReactProvider headers={headers()}>
<ClientProvider>{children}</ClientProvider>
</TRPCReactProvider>
</body>
Inside ClientProvider I have the web3 providers:
// ClientProvider.tsx
...
return (
<WagmiConfig config={config}>
<SIWEProvider {...siweConfig} onSignIn={() => router.push("/")}>
<ConnectKitProvider>{children}</ConnectKitProvider>
</SIWEProvider>
</WagmiConfig>
);
// ClientProvider.tsx
...
return (
<WagmiConfig config={config}>
<SIWEProvider {...siweConfig} onSignIn={() => router.push("/")}>
<ConnectKitProvider>{children}</ConnectKitProvider>
</SIWEProvider>
</WagmiConfig>
);
and here is const siwiConfig. It calls the /siwe route on initial load and sets a cookie using iron-session with the nonce inside, then updates that same cookie after authentiucation with wallet address and chain:
1 Reply
Trader Launchpad
Trader LaunchpadOP10mo ago
const siweConfig = {
getNonce: async () => {
const res = await fetch(`/siwe`, { method: "PUT" });
if (!res.ok) throw new Error("Failed to fetch SIWE nonce");

return res.text();
},
createMessage: ({ nonce, address, chainId }) => {
return new SiweMessage({
nonce,
chainId,
address,
version: "1",
uri: window.location.origin,
domain: window.location.host,
statement: "Sign In With Ethereum to prove you control this wallet.",
}).prepareMessage();
},
verifyMessage: ({ message, signature }) => {
return fetch(`/siwe`, {
method: "POST",
body: JSON.stringify({ message, signature }),
headers: { "Content-Type": "application/json" },
}).then((res) => res.ok);
},
getSession: async () => {
const res = await fetch(`/siwe`);
if (!res.ok) throw new Error("Failed to fetch SIWE session");

const { address, chainId } = await res.json();
return address && chainId ? { address, chainId } : null;
},
signOut: () => fetch(`/siwe`, { method: "DELETE" }).then((res) => res.ok),
} satisfies SIWEConfig;
const siweConfig = {
getNonce: async () => {
const res = await fetch(`/siwe`, { method: "PUT" });
if (!res.ok) throw new Error("Failed to fetch SIWE nonce");

return res.text();
},
createMessage: ({ nonce, address, chainId }) => {
return new SiweMessage({
nonce,
chainId,
address,
version: "1",
uri: window.location.origin,
domain: window.location.host,
statement: "Sign In With Ethereum to prove you control this wallet.",
}).prepareMessage();
},
verifyMessage: ({ message, signature }) => {
return fetch(`/siwe`, {
method: "POST",
body: JSON.stringify({ message, signature }),
headers: { "Content-Type": "application/json" },
}).then((res) => res.ok);
},
getSession: async () => {
const res = await fetch(`/siwe`);
if (!res.ok) throw new Error("Failed to fetch SIWE session");

const { address, chainId } = await res.json();
return address && chainId ? { address, chainId } : null;
},
signOut: () => fetch(`/siwe`, { method: "DELETE" }).then((res) => res.ok),
} satisfies SIWEConfig;
Then i have setup a custom nextjs middleware that checks the cookie, and redirects based on authenticated or unauthenticated status:
const PUBLIC_PATHS = ["/register", "/login", "/reset-password", "/siwe"];
...
const session = await Session.fromRequest(request);
if (session.address) {
// User is authenticated
console.log("Authenticated");
if (isPublicPath) {
// Redirect authenticated users away from public paths to the home page
url.pathname = "/";
return NextResponse.redirect(url);
}
...
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
const PUBLIC_PATHS = ["/register", "/login", "/reset-password", "/siwe"];
...
const session = await Session.fromRequest(request);
if (session.address) {
// User is authenticated
console.log("Authenticated");
if (isPublicPath) {
// Redirect authenticated users away from public paths to the home page
url.pathname = "/";
return NextResponse.redirect(url);
}
...
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
The matcher is stock from nextjs documentation, and it runs on every route including /api and trpc calls. When I have this middleware enabled I am getting an error on the front page of t3 app, on the standard post.hello trpc call:
RequestContentLengthMismatchError: Request body length does not match content-length header
RequestContentLengthMismatchError: Request body length does not match content-length header
When I disable the middleware, this issue goes away. I believe it is due to my nextjs middleware matcher running on every request, and blocking /siwe as a publicpath if they ARE authenticated, sending them to / and something is getitng lost on the trpc side. To clarify on a page reload the middleware is hit multiple times, once on "/", once on "/siwe" which is triggered every page reload by the ClientProvider. The error only happens on the call that originated from "/siwe" Strangely if I simply move /siwe to /api/siwe, the error goes away. Also if I remove the
if (isPublicPath) {
if (isPublicPath) {
logic the error goes away. What is the correct way to setup the nextjs middleware in this situation? Should I just add /siwe as an ignored route in the middleware matcher? Should i be putting my /siwe logic in trpc routes and calling it that way (api.user.getNonce, api.user.verifyNonce. api.user.createSession, ect)?