Next.js SDK Guide
Installation
bash
npm install axios
# or
yarn add axiosServer-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
}