API Routes
API Routes
Section titled “API Routes”The ByteAuth Next.js SDK provides route handlers for QR code generation, webhook processing, and status polling.
Route Handler
Section titled “Route Handler”App Router (Recommended)
Section titled “App Router (Recommended)”Create app/api/byteauth/[...byteauth]/route.ts:
import { ByteAuthHandler } from '@bytefederal/byteauth-nextjs';
export const { GET, POST } = ByteAuthHandler({ callbacks: { async onLogin(user) { console.log('User logged in:', user.publicKey); return user; }, async onRegister(user) { console.log('New user registered:', user.publicKey); return user; }, },});Pages Router
Section titled “Pages Router”Create pages/api/byteauth/[...byteauth].ts:
import { ByteAuthHandler } from '@bytefederal/byteauth-nextjs';
export default ByteAuthHandler({ callbacks: { async onLogin(user) { return user; }, async onRegister(user) { return user; }, },});API Endpoints
Section titled “API Endpoints”The handler creates these endpoints:
| Endpoint | Method | Description |
|---|---|---|
/api/byteauth/qr | GET | Generate QR code |
/api/byteauth/webhook/login | POST | Login webhook |
/api/byteauth/webhook/register | POST | Registration webhook |
/api/byteauth/check | GET | Poll authentication status |
QR Code Generation
Section titled “QR Code Generation”GET /api/byteauth/qr
Section titled “GET /api/byteauth/qr”Generates a new session with QR code data.
Query Parameters:
mode-loginorregister(default:login)sid- Existing session ID to reuse (optional)
Response:
{ "qr": "https://yourapp.com/webhook?session=abc123&challenge=xyz...", "sid": "sess_abc123", "status": 0, "expiresAt": 1699876573}Custom QR Endpoint
Section titled “Custom QR Endpoint”Override the default implementation:
import { generateQRSession } from '@bytefederal/byteauth-nextjs';import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const mode = searchParams.get('mode') || 'login';
const session = await generateQRSession({ mode, domain: process.env.BYTEAUTH_DOMAIN!, challengeLifetime: 30, metadata: { // Custom data to include returnUrl: searchParams.get('returnUrl'), }, });
return NextResponse.json({ qr: session.qrData, sid: session.id, status: 0, expiresAt: session.expiresAt, });}Webhook Handlers
Section titled “Webhook Handlers”POST /api/byteauth/webhook/login
Section titled “POST /api/byteauth/webhook/login”Handles login authentication from ByteVault.
Request Body:
{ "public_key": "04a1b2c3...", "signature": "3045022100...", "challenge": "Sign this to login...", "timestamp": 1699876545}Response:
{ "status": "authenticated", "message": "Login successful"}Custom Webhook Handler
Section titled “Custom Webhook Handler”import { verifySignature, findSession, type WebhookPayload } from '@bytefederal/byteauth-nextjs';import { NextRequest, NextResponse } from 'next/server';import { prisma } from '@/lib/prisma';
export async function POST(request: NextRequest) { const payload: WebhookPayload = await request.json();
// 1. Verify timestamp if (Math.abs(Date.now() / 1000 - payload.timestamp) > 30) { return NextResponse.json( { error: 'Challenge expired' }, { status: 408 } ); }
// 2. Find session by challenge const session = await findSession(payload.challenge); if (!session) { return NextResponse.json( { error: 'Challenge not found' }, { status: 404 } ); }
// 3. Verify signature const isValid = await verifySignature( payload.public_key, payload.signature, payload.challenge );
if (!isValid) { return NextResponse.json( { error: 'Invalid signature' }, { status: 406 } ); }
// 4. Find user const user = await prisma.user.findUnique({ where: { publicKey: payload.public_key }, });
if (!user) { return NextResponse.json( { error: 'User not found' }, { status: 404 } ); }
// 5. Mark session as authenticated await prisma.byteAuthSession.update({ where: { id: session.id }, data: { userId: user.id, authenticatedAt: new Date(), }, });
// 6. Custom logic await prisma.user.update({ where: { id: user.id }, data: { lastLoginAt: new Date() }, });
return NextResponse.json({ status: 'authenticated', message: 'Login successful', });}Status Polling
Section titled “Status Polling”GET /api/byteauth/check
Section titled “GET /api/byteauth/check”Client polls this endpoint to check authentication status.
Query Parameters:
sid- Session ID
Response (pending):
{ "status": "pending", "sid": "sess_abc123"}Response (authenticated):
{ "status": "authenticated", "user": { "id": "user_123", "publicKey": "04a1b2c3..." }, "redirect": "/dashboard"}Custom Check Endpoint
Section titled “Custom Check Endpoint”import { getSession } from '@bytefederal/byteauth-nextjs';import { NextRequest, NextResponse } from 'next/server';import { cookies } from 'next/headers';
export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const sessionId = searchParams.get('sid');
if (!sessionId) { return NextResponse.json( { error: 'Session ID required' }, { status: 400 } ); }
const session = await getSession(sessionId);
if (!session) { return NextResponse.json( { status: 'not_found' }, { status: 404 } ); }
if (session.authenticatedAt && session.user) { // Set auth cookie const cookieStore = cookies(); cookieStore.set('auth-token', session.user.id, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 60 * 60 * 24 * 30, // 30 days });
return NextResponse.json({ status: 'authenticated', user: { id: session.user.id, publicKey: session.user.publicKey, }, redirect: '/dashboard', }); }
return NextResponse.json({ status: 'pending', sid: sessionId, });}Webhook Signature Verification
Section titled “Webhook Signature Verification”Verify that webhooks come from ByteAuth:
import { verifyWebhookSignature } from '@bytefederal/byteauth-nextjs';import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) { const signature = request.headers.get('X-ByteAuth-Signature'); const body = await request.text();
if (!signature) { return NextResponse.json( { error: 'Missing signature' }, { status: 401 } ); }
const isValid = verifyWebhookSignature( body, signature, process.env.BYTEAUTH_WEBHOOK_SECRET! );
if (!isValid) { return NextResponse.json( { error: 'Invalid signature' }, { status: 401 } ); }
// Process webhook...}Error Handling
Section titled “Error Handling”Handle errors gracefully:
import { ByteAuthError } from '@bytefederal/byteauth-nextjs';
export async function POST(request: NextRequest) { try { // ... handle webhook } catch (error) { if (error instanceof ByteAuthError) { return NextResponse.json( { error: error.message }, { status: error.statusCode } ); }
console.error('Unexpected error:', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ); }}Rate Limiting
Section titled “Rate Limiting”Protect endpoints from abuse:
import { Ratelimit } from '@upstash/ratelimit';import { Redis } from '@upstash/redis';import { NextRequest, NextResponse } from 'next/server';
const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(10, '10 s'),});
export async function GET(request: NextRequest) { const ip = request.ip ?? '127.0.0.1'; const { success, limit, remaining } = await ratelimit.limit(ip);
if (!success) { return NextResponse.json( { error: 'Too many requests' }, { status: 429, headers: { 'X-RateLimit-Limit': limit.toString(), 'X-RateLimit-Remaining': remaining.toString(), }, } ); }
// Process request...}