|
| 1 | +--- |
| 2 | +title: Backend Verification |
| 3 | +description: Server-side proof verification patterns and best practices |
| 4 | +--- |
| 5 | + |
| 6 | +## Overview |
| 7 | + |
| 8 | +Backend verification is the process of cryptographically validating proofs received from the Reclaim Protocol after users complete verification. This ensures the authenticity and integrity of the verified data before using it in your application. |
| 9 | + |
| 10 | +<Callout type="success"> |
| 11 | +**Why Backend Verification is Critical** |
| 12 | + |
| 13 | +Always verify proofs on your backend before trusting the data. Client-side verification alone can be bypassed, but cryptographic verification on your server ensures authenticity. |
| 14 | +</Callout> |
| 15 | + |
| 16 | +## Quick Start |
| 17 | + |
| 18 | +For complete backend verification implementations, see: |
| 19 | + |
| 20 | +- **[Node.js / Express Setup →](/js-sdk/recommended-setup/nodejs)** - Complete backend with Express.js |
| 21 | +- **[Next.js Setup →](/js-sdk/recommended-setup/nextjs)** - API routes and verification |
| 22 | +- **[Python Setup →](/js-sdk/recommended-setup/python)** - FastAPI and Django examples |
| 23 | + |
| 24 | +## Verification Process |
| 25 | + |
| 26 | +### 1. Receive Proof |
| 27 | + |
| 28 | +Proofs are sent to your callback URL as URL-encoded JSON: |
| 29 | + |
| 30 | +```javascript |
| 31 | +// Express.js |
| 32 | +app.use(express.text({ type: '*/*', limit: '50mb' })); |
| 33 | + |
| 34 | +app.post('/api/reclaim/callback', async (req, res) => { |
| 35 | + const decodedBody = decodeURIComponent(req.body); |
| 36 | + const proof = JSON.parse(decodedBody); |
| 37 | + // ... |
| 38 | +}); |
| 39 | +``` |
| 40 | + |
| 41 | +### 2. Verify Proof |
| 42 | + |
| 43 | +Use the `verifyProof()` function to cryptographically verify: |
| 44 | + |
| 45 | +```javascript |
| 46 | +import { verifyProof } from '@reclaimprotocol/js-sdk'; |
| 47 | + |
| 48 | +const isValid = await verifyProof(proof); |
| 49 | + |
| 50 | +if (isValid) { |
| 51 | + console.log('✅ Proof is valid'); |
| 52 | + // Process verified data |
| 53 | +} else { |
| 54 | + console.log('❌ Proof verification failed'); |
| 55 | + // Reject the proof |
| 56 | +} |
| 57 | +``` |
| 58 | + |
| 59 | +### 3. Extract Data |
| 60 | + |
| 61 | +Once verified, extract the data you need: |
| 62 | + |
| 63 | +```javascript |
| 64 | +if (isValid) { |
| 65 | + const verifiedData = { |
| 66 | + identifier: proof.identifier, |
| 67 | + provider: JSON.parse(proof.claimData.context).extractedParameters.providerName, |
| 68 | + timestamp: new Date(proof.timestampS * 1000), |
| 69 | + // Extract more fields based on your provider |
| 70 | + }; |
| 71 | + |
| 72 | + // YOUR BUSINESS LOGIC HERE |
| 73 | + // - Save to database |
| 74 | + // - Update user status |
| 75 | + // - Trigger workflows |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +## Complete Example (Node.js) |
| 80 | + |
| 81 | +```javascript |
| 82 | +const express = require('express'); |
| 83 | +const { ReclaimProofRequest, verifyProof } = require('@reclaimprotocol/js-sdk'); |
| 84 | + |
| 85 | +const app = express(); |
| 86 | +app.use(express.text({ type: '*/*', limit: '50mb' })); |
| 87 | + |
| 88 | +// Callback endpoint |
| 89 | +app.post('/api/reclaim/callback', async (req, res) => { |
| 90 | + try { |
| 91 | + // Parse proof |
| 92 | + const decodedBody = decodeURIComponent(req.body); |
| 93 | + const proof = JSON.parse(decodedBody); |
| 94 | + |
| 95 | + console.log('📨 Received proof:', proof.identifier); |
| 96 | + |
| 97 | + // Verify proof |
| 98 | + const isValid = await verifyProof(proof); |
| 99 | + |
| 100 | + if (!isValid) { |
| 101 | + console.error('❌ Proof verification failed'); |
| 102 | + return res.status(400).json({ error: 'Invalid proof' }); |
| 103 | + } |
| 104 | + |
| 105 | + console.log('✅ Proof verified successfully'); |
| 106 | + |
| 107 | + // Extract verified data |
| 108 | + const context = JSON.parse(proof.claimData.context); |
| 109 | + const verifiedData = { |
| 110 | + proofId: proof.identifier, |
| 111 | + provider: context.extractedParameters.providerName, |
| 112 | + verifiedAt: new Date(proof.timestampS * 1000), |
| 113 | + }; |
| 114 | + |
| 115 | + // Save to database (example) |
| 116 | + // await db.verifications.create(verifiedData); |
| 117 | + |
| 118 | + // Update user status (example) |
| 119 | + // await db.users.update({ verified: true }); |
| 120 | + |
| 121 | + return res.json({ success: true }); |
| 122 | + } catch (error) { |
| 123 | + console.error('❌ Error processing proof:', error); |
| 124 | + return res.status(500).json({ error: 'Internal server error' }); |
| 125 | + } |
| 126 | +}); |
| 127 | + |
| 128 | +app.listen(3000, () => { |
| 129 | + console.log('Server running on port 3000'); |
| 130 | +}); |
| 131 | +``` |
| 132 | + |
| 133 | +## Complete Example (Python) |
| 134 | + |
| 135 | +```python |
| 136 | +from fastapi import FastAPI, Request |
| 137 | +from reclaim_python_sdk import verify_proof, Proof |
| 138 | +import json |
| 139 | +from urllib.parse import unquote |
| 140 | + |
| 141 | +app = FastAPI() |
| 142 | + |
| 143 | +@app.post("/api/reclaim/callback") |
| 144 | +async def receive_proofs(request: Request): |
| 145 | + try: |
| 146 | + # Parse proof |
| 147 | + body = await request.body() |
| 148 | + body_str = body.decode('utf-8') |
| 149 | + body_str = unquote(body_str) |
| 150 | + parsed_data = json.loads(body_str) |
| 151 | + |
| 152 | + print(f"📨 Received proof: {parsed_data.get('identifier')}") |
| 153 | + |
| 154 | + # Convert to Proof object |
| 155 | + proof = Proof.from_json(parsed_data) |
| 156 | + |
| 157 | + # Verify proof |
| 158 | + is_valid = await verify_proof(proof) |
| 159 | + |
| 160 | + if not is_valid: |
| 161 | + print("❌ Proof verification failed") |
| 162 | + return {"error": "Invalid proof"}, 400 |
| 163 | + |
| 164 | + print("✅ Proof verified successfully") |
| 165 | + |
| 166 | + # Extract verified data |
| 167 | + verified_data = { |
| 168 | + "proof_id": proof.identifier, |
| 169 | + "provider": proof.claimData.provider, |
| 170 | + "verified_at": datetime.fromtimestamp(proof.timestampS) |
| 171 | + } |
| 172 | + |
| 173 | + # Save to database (example) |
| 174 | + # await db.verifications.create(verified_data) |
| 175 | + |
| 176 | + return {"success": True} |
| 177 | + |
| 178 | + except Exception as error: |
| 179 | + print(f"❌ Error: {error}") |
| 180 | + return {"error": "Internal server error"}, 500 |
| 181 | +``` |
| 182 | + |
| 183 | +## Proof Structure |
| 184 | + |
| 185 | +Understanding the proof object structure: |
| 186 | + |
| 187 | +```typescript |
| 188 | +{ |
| 189 | + identifier: string; // Unique proof ID |
| 190 | + claimData: { |
| 191 | + provider: string; // Provider name |
| 192 | + parameters: string; // JSON string of parameters |
| 193 | + context: string; // JSON string with metadata |
| 194 | + }; |
| 195 | + signatures: string[]; // Cryptographic signatures |
| 196 | + witnesses: [{ // Witness attestations |
| 197 | + address: string; |
| 198 | + signature: string; |
| 199 | + }]; |
| 200 | + timestampS: number; // Unix timestamp (seconds) |
| 201 | +} |
| 202 | +``` |
| 203 | + |
| 204 | +## Security Best Practices |
| 205 | + |
| 206 | +<Callout type="warning"> |
| 207 | +**Critical Security Requirements** |
| 208 | + |
| 209 | +1. **Always verify on backend** - Never trust client-side verification alone |
| 210 | +2. **Validate proof structure** - Check all required fields exist |
| 211 | +3. **Store proofs securely** - Use encrypted database storage |
| 212 | +4. **Check timestamps** - Reject old proofs (set expiration time) |
| 213 | +5. **Rate limit callbacks** - Prevent spam/DOS attacks |
| 214 | +6. **Use HTTPS** - Secure data in transit |
| 215 | +</Callout> |
| 216 | + |
| 217 | +### Timestamp Validation |
| 218 | + |
| 219 | +```javascript |
| 220 | +const MAX_AGE_SECONDS = 300; // 5 minutes |
| 221 | + |
| 222 | +const isValid = await verifyProof(proof); |
| 223 | +const proofAge = Date.now() / 1000 - proof.timestampS; |
| 224 | + |
| 225 | +if (isValid && proofAge <= MAX_AGE_SECONDS) { |
| 226 | + // Proof is valid and recent |
| 227 | +} else if (proofAge > MAX_AGE_SECONDS) { |
| 228 | + console.log('⚠️ Proof is too old'); |
| 229 | +} |
| 230 | +``` |
| 231 | + |
| 232 | +### Proof Deduplication |
| 233 | + |
| 234 | +```javascript |
| 235 | +// Check if proof was already processed |
| 236 | +const existingProof = await db.proofs.findOne({ |
| 237 | + identifier: proof.identifier |
| 238 | +}); |
| 239 | + |
| 240 | +if (existingProof) { |
| 241 | + console.log('⚠️ Proof already processed'); |
| 242 | + return res.status(409).json({ error: 'Proof already processed' }); |
| 243 | +} |
| 244 | + |
| 245 | +// Process and save new proof |
| 246 | +await db.proofs.create({ identifier: proof.identifier, ... }); |
| 247 | +``` |
| 248 | + |
| 249 | +## Database Integration |
| 250 | + |
| 251 | +### Save Verified Proofs |
| 252 | + |
| 253 | +```javascript |
| 254 | +// Mongoose/MongoDB example |
| 255 | +const Verification = mongoose.model('Verification', { |
| 256 | + proofId: { type: String, unique: true }, |
| 257 | + userId: String, |
| 258 | + provider: String, |
| 259 | + verifiedAt: Date, |
| 260 | + proofData: Object |
| 261 | +}); |
| 262 | + |
| 263 | +// In callback |
| 264 | +if (isValid) { |
| 265 | + await Verification.create({ |
| 266 | + proofId: proof.identifier, |
| 267 | + userId: extractUserId(proof), |
| 268 | + provider: extractProvider(proof), |
| 269 | + verifiedAt: new Date(proof.timestampS * 1000), |
| 270 | + proofData: proof |
| 271 | + }); |
| 272 | +} |
| 273 | +``` |
| 274 | + |
| 275 | +## Error Handling |
| 276 | + |
| 277 | +```javascript |
| 278 | +app.post('/api/reclaim/callback', async (req, res) => { |
| 279 | + try { |
| 280 | + const proof = parseProof(req.body); |
| 281 | + |
| 282 | + // Validate structure |
| 283 | + if (!proof || !proof.identifier || !proof.claimData) { |
| 284 | + return res.status(400).json({ error: 'Invalid proof structure' }); |
| 285 | + } |
| 286 | + |
| 287 | + // Verify cryptographically |
| 288 | + const isValid = await verifyProof(proof); |
| 289 | + |
| 290 | + if (!isValid) { |
| 291 | + return res.status(400).json({ error: 'Proof verification failed' }); |
| 292 | + } |
| 293 | + |
| 294 | + // Process proof |
| 295 | + // ... |
| 296 | + |
| 297 | + res.json({ success: true }); |
| 298 | + } catch (error) { |
| 299 | + if (error instanceof SyntaxError) { |
| 300 | + return res.status(400).json({ error: 'Invalid JSON' }); |
| 301 | + } |
| 302 | + |
| 303 | + console.error('Error:', error); |
| 304 | + res.status(500).json({ error: 'Internal server error' }); |
| 305 | + } |
| 306 | +}); |
| 307 | +``` |
| 308 | + |
| 309 | +## Testing |
| 310 | + |
| 311 | +### Manual Testing |
| 312 | + |
| 313 | +```bash |
| 314 | +# Test your callback endpoint |
| 315 | +curl -X POST https://yourapp.com/api/reclaim/callback \ |
| 316 | + -H "Content-Type: application/x-www-form-urlencoded" \ |
| 317 | + -d '{"identifier":"test","claimData":{},"signatures":[],"witnesses":[],"timestampS":1234567890}' |
| 318 | +``` |
| 319 | + |
| 320 | +### Automated Testing |
| 321 | + |
| 322 | +```javascript |
| 323 | +// Jest example |
| 324 | +describe('Proof Verification', () => { |
| 325 | + it('should verify valid proof', async () => { |
| 326 | + const mockProof = { |
| 327 | + identifier: 'test-proof-id', |
| 328 | + claimData: { ... }, |
| 329 | + signatures: ['...'], |
| 330 | + witnesses: [{ ... }], |
| 331 | + timestampS: Date.now() / 1000 |
| 332 | + }; |
| 333 | + |
| 334 | + const response = await request(app) |
| 335 | + .post('/api/reclaim/callback') |
| 336 | + .send(encodeURIComponent(JSON.stringify(mockProof))); |
| 337 | + |
| 338 | + expect(response.status).toBe(200); |
| 339 | + }); |
| 340 | +}); |
| 341 | +``` |
| 342 | + |
| 343 | +## Next Steps |
| 344 | + |
| 345 | +For complete implementation examples: |
| 346 | + |
| 347 | +- **[Node.js Setup →](/js-sdk/recommended-setup/nodejs)** - Full Express.js backend |
| 348 | +- **[Next.js Setup →](/js-sdk/recommended-setup/nextjs)** - API routes implementation |
| 349 | +- **[Python Setup →](/js-sdk/recommended-setup/python)** - FastAPI/Django examples |
| 350 | +- **[API Reference →](/js-sdk/api-reference)** - Complete SDK documentation |
| 351 | +- **[Troubleshooting →](/js-sdk/troubleshooting)** - Common issues |
| 352 | + |
| 353 | +## Need Help? |
| 354 | + |
| 355 | +- 💬 [Community Discord](https://discord.gg/reclaim) |
| 356 | +- 🐛 [GitHub Issues](https://github.com/reclaimprotocol/reclaim-js-sdk/issues) |
0 commit comments