Back to Documentation

Next.js Integration

Server-side and client-side integration guide for Next.js

Next.js SDK Guide

Installation

bash
npm install axios
# or
yarn add axios

Server-Side Usage (API Routes)

typescript
// app/api/validate/route.ts
import { NextRequest, NextResponse } from 'next/server'
import axios from 'axios'
import crypto from 'crypto'

const API_KEY = process.env.KEYCLAIM_API_KEY!
const API_BASE_URL = process.env.KEYCLAIM_BASE_URL || 'https://keyclaim.org/api'

export async function POST(request: NextRequest) {
  try {
    // Create challenge
    const challengeRes = await axios.post(`${API_BASE_URL}/challenge/create`, {
      key: API_KEY,
      ttl: 30
    })
    
    const challenge = challengeRes.data.challenge
    
    // Generate HMAC response
    const response = crypto
      .createHmac('sha256', API_KEY)
      .update(challenge)
      .digest('hex')
    
    // Validate
    const validateRes = await axios.post(`${API_BASE_URL}/challenge/validate`, {
      key: API_KEY,
      challenge: challenge,
      response: response
    })
    
    return NextResponse.json(validateRes.data)
  } catch (error: any) {
    return NextResponse.json(
      { error: error.message },
      { status: 500 }
    )
  }
}

Client-Side Usage (Frontend Purpose Keys)

typescript
// app/components/ChallengeValidation.tsx
'use client'

import { useState } from 'react'

const FRONTEND_TOKEN = process.env.NEXT_PUBLIC_KEYCLAIM_TOKEN!

export default function ChallengeValidation() {
  const [challenge, setChallenge] = useState('')
  const [result, setResult] = useState<any>(null)
  const [loading, setLoading] = useState(false)

  const createChallenge = async () => {
    setLoading(true)
    try {
      const res = await fetch('/api/challenge/frontend-create', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          token: FRONTEND_TOKEN,
          ttl: 30
        })
      })
      const data = await res.json()
      setChallenge(data.challenge)
    } catch (error) {
      console.error('Failed to create challenge:', error)
    } finally {
      setLoading(false)
    }
  }

  const validateChallenge = async () => {
    if (!challenge) return
    
    setLoading(true)
    try {
      const res = await fetch('/api/challenge/frontend-validate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          token: FRONTEND_TOKEN,
          challenge: challenge
        })
      })
      const data = await res.json()
      setResult(data)
    } catch (error) {
      console.error('Validation failed:', error)
    } finally {
      setLoading(false)
    }
  }

  return (
    <div>
      <button onClick={createChallenge} disabled={loading}>
        Create Challenge
      </button>
      {challenge && (
        <>
          <p>Challenge: {challenge}</p>
          <button onClick={validateChallenge} disabled={loading}>
            Validate
          </button>
        </>
      )}
      {result && (
        <p>{result.valid ? '✓ Valid' : '✗ Invalid'}</p>
      )}
    </div>
  )
}

RSA Encryption (Client-Side)

typescript
// For client-side RSA decryption, use Web Crypto API
'use client'

async function decryptChallenge(encryptedChallenge: string, privateKeyPEM: string): Promise<string> {
  // Import private key
  const keyData = pemToArrayBuffer(privateKeyPEM)
  const key = await crypto.subtle.importKey(
    'pkcs8',
    keyData,
    {
      name: 'RSA-OAEP',
      hash: 'SHA-256'
    },
    false,
    ['decrypt']
  )
  
  // Decrypt
  const encryptedBytes = base64ToArrayBuffer(encryptedChallenge)
  const decrypted = await crypto.subtle.decrypt(
    {
      name: 'RSA-OAEP'
    },
    key,
    encryptedBytes
  )
  
  return new TextDecoder().decode(decrypted)
}

function pemToArrayBuffer(pem: string): ArrayBuffer {
  const base64 = pem
    .replace(/-----BEGIN PRIVATE KEY-----/, '')
    .replace(/-----END PRIVATE KEY-----/, '')
    .replace(/\s/g, '')
  return base64ToArrayBuffer(base64)
}

function base64ToArrayBuffer(base64: string): ArrayBuffer {
  const binary = atob(base64)
  const bytes = new Uint8Array(binary.length)
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i)
  }
  return bytes.buffer
}