Next.js

July 20, 2026 · View on GitHub

The Next.js plugin simplifies integrating ALTCHA into your application. It provides handlers and middleware for challenge generation and verification.

Handlers

  • challengeHandler — Generates a new challenge. Configure this endpoint as the widget’s challenge URL.
  • verifyHandler — Optional handler for server-side verification. Useful when external services need to verify a payload.

Middleware

The middleware automatically handles verification during requests.

Features:

  • Validates the payload from the request body or a configured cookie.
  • Sets req.__altcha: AltchaResult, which includes the challenge data and verification result.

Options:

  • throwOnFailure: boolean — Whether to throw an error on verification failure (default: true).

Store

The store marks challenges as used to prevent reuse.

See /docs/store.md for details.

Remote verification (Sentinel)

Pass a verifyServer option to verify Sentinel-issued payloads remotely via Sentinel's /v1/verify/signature API instead of locally with hmacSignatureSecret.

See /docs/server-signatures.md#remote-verification-sentinel-api for details.

Usage

1. Create the altcha instance

File: lib/altcha.ts

import { create, deriveHmacKeySecret, randomInt, CappedMap } from 'altcha-lib/frameworks/nextjs';
import { deriveKey } from 'altcha-lib/algorithms/pbkdf2';

// Define your HMAC secret
const HMAC_SECRET = 'secret.key';

export const altcha = create({
  // Verification HMAC secrets
  hmacSignatureSecret: HMAC_SECRET,
  hmacKeySignatureSecret: await deriveHmacKeySecret(HMAC_SECRET),

  // Adjust challenge parameters
  createChallengeParameters: () => {
    return {
      algorithm: 'PBKDF2/SHA-256',
      // Adjust cost and counter depending on the algorithm
      cost: 5_000,
      counter: randomInt(5_000, 10_000),
      expiresAt: new Date(Date.now() + 600_000), // 10 minutes
    };
  },

  // Key derivation function for the selected algorithm
  deriveKey,

  // Use a cookie instead of form data to send the payload
  setCookie: {
    name: 'altcha',
    path: '/',
  },

  // In distributed environments, use Redis or another shared store
  store: new CappedMap<string, boolean>({
    maxSize: 1_000,
  }),
});

2. Challenge endpoint

File: app/altcha/challenge/route.ts

import { altcha } from '@/src/altcha';
export const GET = altcha.challengeHandler;

3. Standalone verify endpoint

File: app/altcha/verify/route.ts

import { altcha } from '@/src/altcha';
export const POST = altcha.verifyHandler;

4a. Protect a route handler with withMiddleware

File: app/protected/route.ts

import { NextResponse } from 'next/server';
import { altcha } from '@/src/altcha';
import type { AltchaResult } from '@/src/altcha';

export const POST = altcha.withMiddleware(async (req: Request) => {
  return NextResponse.json({
    altcha: (req as { __altcha: AltchaResult }).__altcha,
  });
});

4b. Use middleware in Next.js middleware.ts

File: middleware.ts

import { NextRequest, NextResponse } from 'next/server';
import { altcha } from '@/src/altcha';

export async function middleware(req: NextRequest) {
  if (req.method === 'POST') {
    const result = await altcha.middleware(req);
    if (result instanceof Response) {
      return result; // verification failed — 403 response
    }
  }
  return NextResponse.next();
}

export const config = {
  matcher: ['/api/protected/:path*'],
};