A full-stack, production-grade application integrating Setu's Aadhaar eSign APIs for digital document signing.
This platform enables users to securely upload contracts, sign them via Aadhaar, and instantly download the legally binding documents—all while keeping sensitive API credentials completely hidden from the browser.
The application follows a secure, scalable 3-tier architecture:
- Frontend (Next.js 15 App Router): A dumb, highly-responsive client. It handles the UI, file selection, and real-time database listeners, but never communicates with Setu directly. It holds only short-lived Firebase ID tokens.
- Backend (Firebase Cloud Functions - Node 20): The secure intermediary. It proxies all requests to Setu, processes webhooks, and securely streams signed PDF binaries back to the client.
- Database (Firebase Firestore): A NoSQL database that stores request metadata, enabling real-time UI updates without polling overhead.
graph TD;
Client[Next.js Frontend] -->|Firebase ID Token| Backend[Firebase Functions]
Backend -->|Secret API Keys| Setu[Setu eSign APIs]
Setu -->|S3 Pre-signed URL| Backend
Backend -->|Streams PDF Binary| Client
Backend -->|Writes Metadata| DB[(Firestore)]
Client -.->|Real-time Listener| DB
┌───────────────────────────────────────────────────────────────┐
│ Browser │
│ Next.js App (App Router) │
│ Pages: / (landing) | /upload | /status │
│ Auth: Firebase Auth SDK (client-side ONLY for tokens) │
│ NEVER calls Setu or holds secrets │
└───────────────────────┬───────────────────────────────────────┘
│ Firebase ID Token in every request header
┌───────────────────────▼───────────────────────────────────────┐
│ Firebase Cloud Functions (Backend) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Middleware Chain (applied to every function) │ │
│ │ 1. verifyFirebaseToken() → Auth guard │ │
│ │ 2. ipRateLimit() → 20 req/min per IP │ │
│ │ 3. uidRateLimit() → 10 req/min per UID │ │
│ │ 4. validateInput() → Zod schema check │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ Functions: │
│ POST /uploadContract → upload doc + create sig request │
│ GET /signatureStatus → fetch status from Setu │
│ GET /downloadDocument → stream signed PDF from Setu │
│ GET /listRequests → user's history from Firestore │
│ POST /webhook/setu → receive Setu push updates [Bonus] │
│ │
│ All Setu calls proxied through: src/lib/setu.ts │
│ All DB calls proxied through: src/lib/db.ts │
└──────────┬────────────────────────────┬───────────────────────┘
│ HTTPS │ Firestore Admin SDK
┌──────────▼──────────┐ ┌────────────▼─────────────────────┐
│ Setu APIs │ │ Firestore (Database) │
│ dg-sandbox.setu.co │ │ Collection: signature_requests │
│ POST /api/documents │ │ Collection: rate_limits │
│ POST /api/signature │ │ Collection: users │
│ GET /api/signature │ └──────────────────────────────────┘
│ GET /api/documents │
│ /:id/download │
└─────────────────────┘
- Frontend: Next.js 15 (React 19)
- Chosen for its App Router, seamless API integration capabilities, and rich ecosystem.
- Styling: Tailwind CSS
- Enables rapid UI development with utility classes, giving the app a premium, modern glassmorphism aesthetic without heavy CSS payloads.
- Backend Environment: Firebase Cloud Functions (Node.js)
- A serverless architecture that scales automatically from zero to millions of requests, eliminating infrastructure management overhead.
- Authentication: Firebase Auth
- Provides highly secure, out-of-the-box JWT authentication via Google Sign-in and Email/Password.
- Database: Firebase Firestore (NoSQL)
- Decision: We opted for Firestore over Postgres/SQL because Firestore natively supports Real-time WebSockets. This allows the frontend to instantly react when a document is signed, completely bypassing traditional, resource-heavy HTTP polling.
- Validation: Zod
- Used heavily in the backend to rigorously type-check and sanitize all incoming network payloads before they are processed.
Security is treated as a first-class citizen in this application:
- No Client-Side Secrets:
SETU_CLIENT_IDandSETU_CLIENT_SECRETare strictly locked inside Firebase Function Environment Configs. The frontend is blind to them. - Magic Byte File Verification: Before uploading a file to Setu, the backend parses the raw binary buffer for
%PDF-magic bytes. If a user renames a malicious executable (virus.exe->virus.pdf), the server instantly rejects it. - Stateless Auth Guards: Every backend route validates the user's Firebase ID token via the Firebase Admin SDK before executing logic.
- IDOR Prevention: The backend checks the Firestore database to ensure the
uidof the requesting user matches theuidof the document owner before authorizing a download. - No Raw Errors Exposed: If Setu throws an error, the raw stack trace is logged on the server. The client only receives a generic, sanitized HTTP error to prevent information leakage.
- Robust
.gitignore: Strict global ignore rules ensurefirebase-adminsdk.json,.env, and private keys are never pushed to version control.
(For a production scale environment, I would further implement Firebase App Check for device attestation, and Google Cloud Armor to enforce IP-based rate limiting against DDoS attacks).
- Node.js 18+
- A Firebase Project (Must be on the Blaze Pay-as-you-go plan to enable external outbound HTTP requests)
- Setu Sandbox Credentials
git clone <your-repo-url>
cd mg-intern-assignment
# Install Frontend Dependencies
cd frontend
npm install
# Install Backend Dependencies
cd ../functions
npm installFrontend (frontend/.env.local):
Create a .env.local file in the frontend directory using your Firebase project details:
NEXT_PUBLIC_FIREBASE_API_KEY="your-api-key"
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN="your-project.firebaseapp.com"
NEXT_PUBLIC_FIREBASE_PROJECT_ID="your-project"
NEXT_PUBLIC_FIREBASE_APP_ID="your-app-id"
NEXT_PUBLIC_FUNCTIONS_BASE_URL="http://127.0.0.1:5001/your-project/asia-south1" # Or production URLBackend (Firebase Configs): Deploy your Setu secrets directly into the Firebase environment:
firebase functions:config:set \
setu.client_id="your-client-id" \
setu.client_secret="your-client-secret" \
setu.product_instance_id="your-instance-id" \
setu.base_url="https://dg-sandbox.setu.co"Start the Next.js Frontend:
cd frontend
npm run devStart the Firebase Backend Emulator:
cd functions
npm run build
firebase emulators:start --only functionsThe application will now be running at http://localhost:3000.
- ✅ Database Persistence: All signature request metadata is persistently stored in Firestore.
- ✅ Zero-Read Realtime History: Refactored the UI to bypass API requests entirely for the Status page, instead using a direct, secure
onSnapshotFirestore websocket to render the history natively in 0ms with zero cold-starts. - ✅ Pre-signed URL Stream Abstraction: Handled the undocumented Setu Sandbox behavior where the API returns a JSON S3 pre-signed URL instead of a binary stream, securely fetching the stream server-side and piping it back to the client.