Documentation menuReact and Next.js

React and Next.js

A client component that gets a token on submit, verified in a Server Action or a Route Handler.

On this page

Server helper #

Keep verification in a server-only module, so the secret never reaches a client bundle:

lib/blockalt.tsTypeScript
import "server-only";

export async function verifyBlockAlt(token: string, action: string, remoteip?: string) {
  try {
    const response = await fetch("https://blockalt.click/v1/siteverify", {
      method: "POST",
      cache: "no-store",
      redirect: "error",
      headers: {
        Authorization: `Bearer ${process.env.BLOCKALT_SECRET}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ token, hostname: "example.com", action, ...(remoteip ? { remoteip } : {}) }),
      signal: AbortSignal.timeout(3000),
    });
    const result = await response.json();
    return response.ok && result.success === true;
  } catch {
    return false;
  }
}

Client component #

app/signup/signup-form.tsxTSX
"use client";
import Script from "next/script";
import { useRef } from "react";
import { signup } from "./actions";

declare global {
  interface Window { BlockAlt: any }
}

export function SignupForm() {
  const protection = useRef<any>(null);
  async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    protection.current ??= window.BlockAlt.create({
      siteKey: process.env.NEXT_PUBLIC_BLOCKALT_SITE_KEY!,
      apiUrl: "https://blockalt.click",
      configPublicKeys: process.env.NEXT_PUBLIC_BLOCKALT_CONFIG_KEYS!.split(","),
    });
    const data = new FormData(event.currentTarget);
    data.set("blockalt-token", await protection.current.execute("signup"));
    await signup(data);
  }
  return (
    <>
      <Script
        src="https://blockalt.click/v1/assets/blockalt.c8b2d9539dbbc932999adb0f51a36bddd4e99899f543e1505818841878cf9813.js"
        integrity="sha384-pblByFDqcJxRfhDh18GH3gRsWy9ZaS4AjDc6OpozzQqvkuYvyin+4BWcKXxA+bN8"
        crossOrigin="anonymous"
        strategy="afterInteractive"
      />
      <form onSubmit={onSubmit}>
        <input name="email" type="email" required />
        <button>Create account</button>
      </form>
    </>
  );
}

Server Action #

app/signup/actions.tsTypeScript
"use server";
import { verifyBlockAlt } from "@/lib/blockalt";

export async function signup(data: FormData) {
  const token = data.get("blockalt-token");
  if (!(await verifyBlockAlt(typeof token === "string" ? token : "", "signup"))) {
    return { error: "Please try again." };
  }
  // …create the account
}

Route Handler #

For a JSON API called with fetch:

app/api/signup/route.tsTypeScript
import { verifyBlockAlt } from "@/lib/blockalt";

export async function POST(request: Request) {
  const body = await request.json().catch(() => ({}));
  if (!(await verifyBlockAlt(typeof body.token === "string" ? body.token : "", "signup"))) {
    return Response.json({ error: "verification_failed" }, { status: 403 });
  }
  // …create the account
  return Response.json({ ok: true });
}

With a Content-Security-Policy in middleware.ts, add the CSP entries; next/script passes your nonce to the script tag.

Something unclear or missing on this page? Write to support@blockalt.click.