AuthenlySign Developer Guide
Version: 2.0 Last Updated: February 2026 Target Audience: Developers, Integration Engineers, Technical Architects
Table of Contents
- Architecture Overview
- Technology Stack
- Local Development Setup
- Directory Structure
- Database Schema
- API Reference
- Authentication & Authorization
- Webhooks
- Security Architecture
- Testing
- Deployment
- Contributing
Architecture Overview
+---------------------------------------------------+
| Frontend |
| Next.js 16 App Router + React 19.2 RSC |
+------------------------+--------------------------+
|
+------------------------+--------------------------+
| Proxy (Edge Middleware) |
| WAF -> Rate Limiter -> Supabase Session Refresh |
+--------+----------+-----------+-------------------+
| | |
+------+---+ +---+-----+ +--+------------+
| Supabase | | Stripe | | Vercel Blob |
| Postgres | | Payments| | File Storage |
| + Auth | | | | |
+----------+ +---------+ +---------------+
|
+------+--------+
| Upstash Redis |
| Rate Limiting |
| + Caching |
+---------------+Data Flow: Document Signing
- User uploads PDF via
/dashboard/documents - File stored in Vercel Blob; document record created in PostgreSQL
- Sender adds fields and signers, then sends
- Each signer receives an email with a secure link to
/sign/[documentId] - Signer completes fields, submits
POST /api/documents/[documentId]/signvalidates input, generates PKI digital signature, creates SHA-256 hash, stamps with trusted timestamp authority- Compliance logger records the event with hash-chain integrity
- Completion notifications sent; signed PDF generated
Technology Stack
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, Turbopack) |
| UI | React 19.2, TypeScript 5.x, Tailwind CSS v4, shadcn/ui |
| Database | Supabase PostgreSQL (60 tables, full RLS) |
| Auth | Supabase Auth (email/password, SAML SSO, 2FA) |
| File Storage | Vercel Blob |
| Payments | Stripe (Checkout, Customer Portal, Webhooks) |
| Resend (transactional email) | |
| Cache/Rate Limit | Upstash Redis (lazy-loaded, dynamic import) |
| AI | Vercel AI Gateway (fraud detection, document analysis) |
| Hosting | Vercel (serverless functions + Edge) |
Local Development Setup
Prerequisites
- Node.js 18+ with npm, pnpm, or yarn
- A Supabase project (for database and auth)
- A Stripe account (for payment features)
- Git
Step 1: Clone and Install
git clone https://github.com/your-org/authenlysign.git
cd authenlysign
pnpm installStep 2: Environment Variables
Create .env.local with:
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
# App
NEXT_PUBLIC_SITE_URL=http://localhost:3000
# Stripe
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Vercel Blob
BLOB_READ_WRITE_TOKEN=vercel_blob_...
# Email
RESEND_API_KEY=re_...
# Security
CRON_SECRET=random_secret_for_cron_jobsStep 3: Database Migrations
Run the SQL scripts in the /scripts directory in numerical order against your Supabase SQL Editor. There are 82+ migration files covering all 60 tables, RLS policies, triggers, and indexes.
Step 4: Start Development
pnpm devVisit http://localhost:3000. Create a test account, upload a PDF, and complete the full signing flow to verify setup.
Directory Structure
authenlysign/
+-- app/ # Next.js App Router (104 pages)
| +-- api/ # API route handlers (159 routes)
| | +-- admin/ # Admin-only endpoints
| | +-- auth/ # Authentication endpoints
| | +-- documents/ # Document CRUD + signing
| | +-- cron/ # Scheduled jobs
| | +-- health/ # Health check endpoints
| | +-- security/ # Security monitoring
| | +-- webhooks/ # Webhook handlers
| +-- auth/ # Auth pages (signin, signup, verify, etc.)
| +-- dashboard/ # Protected dashboard pages
| | +-- admin/ # Admin panels
| | +-- documents/ # Document management
| | +-- templates/ # Template management
| | +-- settings/ # User and org settings
| +-- sign/[documentId]/ # Public signing ceremony
| +-- layout.tsx # Root layout with fonts + metadata
| +-- page.tsx # Landing page
+-- components/ # React components (120 files)
| +-- ui/ # shadcn/ui primitives
+-- lib/ # Server utilities (106 modules)
| +-- supabase/ # Supabase client/server/proxy/admin
| +-- api-auth.ts # requireAuth / requireAdmin guards
| +-- input-sanitizer.ts # SQL/XSS/CMD/Path injection detection
| +-- compliance-logger.ts # Hash-chained audit trail
| +-- waf.tsx # Web Application Firewall engine
| +-- pki-manager.ts # PKI digital signatures
| +-- webhook*.ts # Webhook delivery and retry
+-- hooks/ # React hooks (use-auth, use-mobile, etc.)
+-- scripts/ # SQL migrations (82+ files)
+-- docs/ # Additional documentation
+-- proxy.ts # Edge middleware (WAF + rate limit + auth)
+-- next.config.mjs # Next.js config with security headersDatabase Schema
The application uses 60 PostgreSQL tables managed via Supabase. All tables have Row Level Security (RLS) enabled with appropriate policies.
Core Tables
| Table | Purpose |
|---|---|
documents | Document records (title, status, file URL, expiration) |
document_signers | Signer assignments per document |
document_field_data | Field values captured during signing |
document_fields | Field definitions (type, position, page) |
document_templates | Reusable document templates |
user_profiles | Extended user data (name, company, role, plan) |
organizations | Organization/team records |
audit_logs | Immutable audit trail with hash chain |
signing_sessions | Active signing session tracking |
signer_authentication | Signer identity verification records |
Key Relationships
auth.users (Supabase Auth)
|-- user_profiles (1:1, auto-created via trigger)
|-- documents (1:many)
| |-- document_signers (1:many)
| |-- document_fields (1:many)
| |-- document_field_data (1:many)
| |-- audit_logs (1:many)
|-- organizations (many:many via org_members)RLS Pattern
All tables follow the ownership pattern:
-- Users can only access their own data
CREATE POLICY "users_own_data" ON documents
FOR ALL USING (auth.uid() = user_id);
-- Signers can access documents they are assigned to
CREATE POLICY "signers_access" ON documents
FOR SELECT USING (
id IN (SELECT document_id FROM document_signers WHERE signer_email = auth.email())
);API Reference
Authentication
All API requests require one of:
- Session Cookie (browser): Automatically managed by Supabase Auth
- API Key (programmatic):
Authorization: Bearer sk_live_... - Service Role (server-to-server):
Authorization: Bearer <service_role_key>
Core Endpoints
Documents
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/documents/upload | Upload a new PDF |
| GET | /api/documents/[id] | Get document details |
| POST | /api/documents/[id]/send-for-signing | Send to signers |
| POST | /api/documents/[id]/sign | Submit signature |
| GET | /api/documents/[id]/status | Check signing status |
| POST | /api/documents/[id]/remind | Send reminder |
| GET | /api/documents/[id]/download | Download signed PDF |
| POST | /api/documents/[id]/verify | Verify signature chain |
Templates
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/templates/create | Create template |
| GET | /api/templates/[id] | Get template |
| PUT | /api/templates/[id]/update | Update template |
Users & Auth
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/user/profile | Get current user profile |
| PUT | /api/user/profile | Update profile |
| POST | /api/auth/2fa/setup | Initialize 2FA setup |
| POST | /api/auth/2fa/verify | Verify 2FA code |
Subscriptions
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/subscription/create | Create subscription |
| POST | /api/subscription/change | Change plan |
| POST | /api/subscription/cancel | Cancel subscription |
| POST | /api/subscription/portal | Stripe Customer Portal |
Admin (requires admin role)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/stats | Organization statistics |
| GET | /api/admin/users | List all users |
| POST | /api/admin/users/create | Create user |
| POST | /api/admin/users/bulk-import | Bulk import users |
| GET | /api/admin/security/events | Security event log |
Health Checks
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/health | Overall health |
| GET | /api/health/database | Database connectivity |
| GET | /api/health/security | Security posture (admin) |
| GET | /api/health/production | Full production check |
Error Format
{
"error": "DOCUMENT_NOT_FOUND",
"message": "Document not found or access denied",
"details": { "documentId": "..." }
}Common codes: UNAUTHORIZED, FORBIDDEN, NOT_FOUND, VALIDATION_ERROR, RATE_LIMIT_EXCEEDED, INTERNAL_ERROR.
Rate Limiting
- Default: 1000 requests/hour per API key, 100/hour per IP (unauthenticated)
- Headers:
X-RateLimit-Remaining,X-RateLimit-Reset - 429 response includes
Retry-Afterheader
Authentication & Authorization
Supabase Auth Clients
// Browser client (singleton)
import { createClient } from "@/lib/supabase/client"
const supabase = createClient()
// Server client (per-request)
import { createServerSupabaseClient } from "@/lib/supabase/server"
const supabase = await createServerSupabaseClient()
// Service role client (admin operations, bypasses RLS)
import { createServiceRoleClient } from "@/lib/supabase/server"
const supabase = createServiceRoleClient()API Route Guards
import { requireAuth, requireAdmin, isAuthError } from "@/lib/api-auth"
// Require authenticated user
export async function GET() {
const auth = await requireAuth()
if (isAuthError(auth)) return auth
// auth.user and auth.supabase are available
}
// Require admin role
export async function POST() {
const auth = await requireAdmin()
if (isAuthError(auth)) return auth
// Only admins reach here
}Dashboard Protection
The dashboard layout (app/dashboard/layout.tsx) is an async Server Component that:
- Calls
supabase.auth.getUser()on every request - Redirects to
/auth/signinif no user is found - Fetches the user profile for display (name, role, admin badge)
The proxy middleware (proxy.ts) also refreshes Supabase sessions and redirects unauthenticated users away from /dashboard/*.
Webhooks
Event Types
| Event | Trigger |
|---|---|
document.created | New document uploaded |
document.sent | Document sent for signing |
document.signed | One signer completed |
document.completed | All signers completed |
document.expired | Document past expiration |
document.voided | Document cancelled |
user.created | New user signed up |
subscription.changed | Plan upgraded/downgraded |
Payload Structure
{
"id": "evt_abc123",
"type": "document.completed",
"created": 1709251200,
"data": {
"id": "doc_abc123",
"title": "NDA Agreement",
"status": "signed",
"completedAt": "2026-02-19T12:00:00Z"
}
}Signature Verification
import crypto from "crypto"
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const expected = crypto.createHmac("sha256", secret).update(payload).digest("hex")
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(\`sha256=\${expected}\`))
}Retry Policy
Failed deliveries retry with exponential backoff: immediately, 5 min, 30 min, 2 hours, 6 hours. After 5 failures, the delivery moves to the dead-letter queue (/api/webhooks/dead-letter-queue).
Security Architecture
Proxy Middleware (proxy.ts)
All requests pass through the Edge proxy which runs three checks in order:
- WAF Inspection:
analyzeRequest()fromlib/waf.tsxscans URL, method, and headers for injection patterns. Blocked requests get HTTP 403. - Rate Limiting:
checkRateLimit()fromlib/proxy-rate-limit.tsenforces per-IP limits using in-memory counters (Redis optional). - Session Refresh:
updateSession()fromlib/supabase/proxy.tsrefreshes the Supabase JWT and redirects unauthenticated users from protected routes.
Input Sanitization (lib/input-sanitizer.ts)
Available for use in any API route:
import { validateInput, validateRequestBody } from "@/lib/input-sanitizer"
const result = validateInput(userInput)
if (!result.safe) {
// result.threats contains ["sql_injection", "xss", etc.]
}
const bodyResult = validateRequestBody(requestBody) // Recursive check of all valuesSecurity Headers (next.config.mjs)
Production headers include: HSTS (1 year, includeSubDomains), CSP (script-src, style-src, connect-src, frame-ancestors), X-Frame-Options DENY, X-Content-Type-Options nosniff, Referrer-Policy strict-origin-when-cross-origin, Permissions-Policy (camera, microphone, geolocation disabled), X-Download-Options noopen, X-Permitted-Cross-Domain-Policies none, upgrade-insecure-requests.
Compliance Logging (lib/compliance-logger.ts)
All security and compliance events are logged with SHA-256 hash chaining:
import { logComplianceEvent } from "@/lib/compliance-logger"
await logComplianceEvent({
documentId: "...",
action: "document_signed",
eventType: "document_signed",
actorEmail: "signer@example.com",
actorIpAddress: ip,
details: { certificate_id: "...", document_hash: "..." },
})Testing
Health Check Endpoints
Use the built-in health endpoints for integration testing:
curl https://your-app.vercel.app/api/health
curl https://your-app.vercel.app/api/health/database
curl https://your-app.vercel.app/api/health/security # Requires admin authE2E Testing
The admin E2E testing panel at /dashboard/admin/e2e-testing provides automated test suites for:
- Authentication flows (signup, signin, 2FA, password reset)
- Document lifecycle (upload, send, sign, verify, download)
- Email delivery (invitation, reminder, completion)
- User management (create, update, delete, bulk import)
Security Test Suite
The /api/health/security endpoint runs 11 automated checks including:
- WAF engine validation (6 attack vectors)
- Injection defense testing (25+ payloads: SQL, XSS, CMD, path traversal)
- Parameterized query verification
- RLS policy coverage
- Encryption standards
- Audit trail integrity
Deployment
Vercel Deployment
- Connect your GitHub repository to Vercel
- Environment variables are injected automatically from integrations (Supabase, Stripe, Blob, Upstash)
- Push to main branch triggers production deployment
- Preview deployments created for pull requests
Post-Deployment Verification
# Check overall health
curl https://your-app.vercel.app/api/health
# Check production readiness
curl https://your-app.vercel.app/api/health/production
# Check security posture (requires admin cookie)
curl -H "Cookie: ..." https://your-app.vercel.app/api/health/securityEnvironment Variables
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Yes | Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Yes | Supabase anonymous key |
SUPABASE_SERVICE_ROLE_KEY | Yes | Supabase service role key |
STRIPE_SECRET_KEY | Yes | Stripe secret key |
BLOB_READ_WRITE_TOKEN | Yes | Vercel Blob token |
RESEND_API_KEY | Yes | Resend email API key |
CRON_SECRET | Yes | Secret for cron job auth |
KV_REST_API_URL | Optional | Upstash Redis URL |
KV_REST_API_TOKEN | Optional | Upstash Redis token |
Contributing
Development Workflow
- Create a feature branch from
main - Make changes following existing code patterns
- Ensure all health endpoints pass
- Open a pull request with a clear description
- Vercel creates a preview deployment automatically
- Request code review from a team member
- Merge to
mainafter approval
Code Style
- TypeScript with strict mode
- Tailwind CSS v4 with design tokens (use
bg-background,text-foreground, etc. -- never direct colors) - shadcn/ui components as primitives
- Server Components by default;
"use client"only when needed - Supabase JS client for all database access (parameterized queries by default)
- Dynamic imports for heavy/optional modules (e.g.,
await import("@upstash/redis"))
Commit Conventions
Use conventional commits: feat:, fix:, docs:, refactor:, chore:, security:
Adding a New API Route
- Create
app/api/your-feature/route.ts - Import and call
requireAuth()orrequireAdmin()from@/lib/api-auth - Validate input with
validateRequestBody()from@/lib/input-sanitizer - Use
supabase.from("table")for database access (never raw SQL) - Log compliance events with
logComplianceEvent()for sensitive actions - Return structured JSON responses with appropriate HTTP status codes
AuthenlySign Developer Guide v2.0 -- February 2026 For the latest version, visit /resources/docs/developer-guide
