# Reclaim Protocol JavaScript SDK Integration Guide This guide will walk you through integrating the Reclaim Protocol JavaScript SDK into your application. We'll create a simple React application that demonstrates how to use the SDK to generate proofs and verify claims. [Official documentation](https://docs.reclaimprotocol.org/js-sdk/installation) ## Prerequisites Before we begin, make sure you have: 1. An application ID from Reclaim Protocol. 2. An application secret from Reclaim Protocol. 3. A provider ID for the specific service you want to verify. You can obtain these details from the [Reclaim Developer Portal](https://dev.reclaimprotocol.org/). ## Step 1: Create a new React application Let's start by creating a new React application: ```bash npx create-react-app reclaim-app cd reclaim-app ``` ## Step 2: Install necessary dependencies Install the Reclaim Protocol SDK and a QR code generator: ```bash npm install @reclaimprotocol/js-sdk react-qr-code ``` **Current SDK Version**: 5.2.0 ## Step 3: Set up your React component Replace the contents of `src/App.js` with the following code: ```javascript import React, { useState, useEffect } from "react"; import { ReclaimProofRequest, verifyProof, ClaimCreationType } from "@reclaimprotocol/js-sdk"; import QRCode from "react-qr-code"; function App() { const [reclaimProofRequest, setReclaimProofRequest] = useState(null); const [requestUrl, setRequestUrl] = useState(""); const [statusUrl, setStatusUrl] = useState(""); const [proofs, setProofs] = useState(null); useEffect(() => { async function initializeReclaim() { const APP_ID = "YOUR_APPLICATION_ID_HERE"; const APP_SECRET = "YOUR_APPLICATION_SECRET_HERE"; const PROVIDER_ID = "YOUR_PROVIDER_ID_HERE"; const proofRequest = await ReclaimProofRequest.init(APP_ID, APP_SECRET, PROVIDER_ID); setReclaimProofRequest(proofRequest); } initializeReclaim(); }, []); async function handleCreateClaim() { if (!reclaimProofRequest) { console.error("Reclaim Proof Request not initialized"); return; } const url = await reclaimProofRequest.getRequestUrl(); setRequestUrl(url); const status = reclaimProofRequest.getStatusUrl(); setStatusUrl(status); console.log("Status URL:", status); await reclaimProofRequest.startSession({ onSuccess: (proofs) => { if (proofs && typeof proofs !== "string") { // When using the default callback url, we get a proof if (Array.isArray(proofs)) { if (proofs.length == 0) { // From version 4.10.1, this is the case when using a custom callback url // Proofs are sent to the callback url. console.log("No proofs received. This is expected when using a custom callback url."); } else { // when using the cascading providers, providers having more than one proof will return an array of proofs console.log(JSON.stringify(proofs.map((p) => p.claimData.context))); } } else { console.log("Proof received:", proofs?.claimData.context); } setProofs(proofs); } }, onError: (error) => { console.error("Verification failed", error); }, }); } return (

Reclaim Protocol Demo

{requestUrl && (

Scan this QR code to start the verification process:

)} {proofs && (

Verification Successful!

{JSON.stringify(proofs, null, 2)}
)}
); } export default App; ``` ## Step 4: Understanding the code Let's break down what's happening in this code: 1. We initialize the Reclaim SDK with your application ID, secret, and provider ID. This happens once when the component mounts. 2. When the user clicks the "Create Claim" button, we: - Generate a request URL using `getRequestUrl()`. This URL is used to create the QR code. - Get the status URL using `getStatusUrl()`. This URL can be used to check the status of the claim process. - Start a session with `startSession()`, which sets up callbacks for successful and failed verifications, and allows you to pass an optional `verificationConfig` to customize proof verification. 3. We display a QR code using the request URL. When a user scans this code, it starts the verification process. 4. The status URL is logged to the console. You can use this URL to check the status of the claim process programmatically. 5. When the verification is successful, we display the proof data on the page. ## Step 5: New Streamlined Flow with Browser Extension Support The Reclaim SDK now provides a simplified `triggerReclaimFlow()` method that automatically handles the verification process across different platforms and devices. This method intelligently chooses the best verification method based on the user's environment. ### Using triggerReclaimFlow() Replace the `handleCreateClaim` function in your React component with this simpler approach: ```javascript async function handleCreateClaim() { if (!reclaimProofRequest) { console.error("Reclaim Proof Request not initialized"); return; } try { // Start the verification process automatically await reclaimProofRequest.triggerReclaimFlow(); // Listen for the verification results await reclaimProofRequest.startSession({ onSuccess: (proofs) => { if (proofs && typeof proofs !== "string") { if (Array.isArray(proofs)) { if (proofs.length == 0) { // proofs sent to callback url } else { console.log(JSON.stringify(proofs.map((p) => p.claimData.context))); } } else { console.log("Proof received:", proofs?.claimData.context); } setProofs(proofs); } }, onError: (error) => { console.error("Verification failed", error); }, }); } catch (error) { console.error("Error triggering Reclaim flow:", error); } } ``` ### How triggerReclaimFlow() Works The `triggerReclaimFlow()` method supports two verification modes via `verificationMode`: - **`'portal'` (default)**: Opens the portal URL for remote browser verification. Opens in a new tab by default, or embedded in an iframe when `target` is provided. - **`'app'`**: Verifier app flow via the share page. Uses App Clip on iOS if `useAppClip` is `true`. The method returns a `FlowHandle` that lets you close the flow programmatically: ```javascript // Portal flow (default) — opens in new tab const handle = await reclaimProofRequest.triggerReclaimFlow(); handle.tab; // Window reference to the opened tab handle.close(); // close tab and stop polling // Embedded — portal loads inside a DOM element as an iframe const handle = await reclaimProofRequest.triggerReclaimFlow({ target: document.getElementById('reclaim-container') }); handle.iframe; // HTMLIFrameElement reference handle.iframe?.style.height = '700px'; // customize the iframe handle.close(); // remove iframe and stop polling // Verifier app flow const handle = await reclaimProofRequest.triggerReclaimFlow({ verificationMode: 'app' }); handle.close(); // stop polling ``` #### On Desktop Browsers: 1. **Embedded mode**: If `target` is provided, the portal loads in an iframe inside the target element. 2. **Browser Extension**: If the Reclaim browser extension is installed and no `target` is set, it will use the extension. 3. **Portal mode** (no extension, no target): Opens the portal in a new tab. 4. **App mode** (no extension): Shows QR code modal with share page URL. #### On Mobile Devices (portal mode): Opens the portal in a new tab, or in an iframe if `target` is provided. #### On Mobile Devices (app mode): 1. **All platforms**: Redirects to the verifier app. 2. **iOS with `useAppClip: true`**: Redirects to the Reclaim App Clip instead. The same `verificationMode` option works with `getRequestUrl()`: ```javascript // Portal URL (default) const url = await reclaimProofRequest.getRequestUrl(); // Verifier app URL const url = await reclaimProofRequest.getRequestUrl({ verificationMode: 'app' }); ``` ### Browser Extension Support The SDK now includes built-in support for the Reclaim browser extension, providing users with a seamless verification experience without leaving their current browser tab. #### Features: - **Automatic Detection**: The SDK automatically detects if the Reclaim browser extension is installed - **Seamless Integration**: No additional setup required - the extension integration works out of the box - **Fallback Support**: If the extension is not available, the SDK gracefully falls back to QR code or mobile app flows #### Manual Extension Detection: You can also manually check if the browser extension is available: ```javascript const isExtensionAvailable = await reclaimProofRequest.isBrowserExtensionAvailable(); if (isExtensionAvailable) { console.log("Reclaim browser extension is installed"); } else { console.log("Browser extension not available, will use alternative flow"); } ``` #### Configuring Browser Extension Options: You can customize the browser extension behavior during SDK initialization: ```javascript const proofRequest = await ReclaimProofRequest.init(APP_ID, APP_SECRET, PROVIDER_ID, { useBrowserExtension: true, // Enable/disable browser extension (default: true) extensionID: "custom-extension-id", // Use custom extension ID if needed // ... other options }); ``` ### Modal Customization When the QR code modal is displayed (fallback on desktop), you can customize its appearance and behavior: ```javascript // Set modal options before triggering the flow reclaimProofRequest.setModalOptions({ title: "Custom Verification Title", description: "Scan this QR code with your mobile device to verify your account", darkTheme: true, // Enable dark theme (default: false) modalPopupTimer: 5, // Auto-close modal after 5 minutes (default: 1 minute) showExtensionInstallButton: true, // Show extension install button (default: false) extensionUrl: "https://custom-extension-url.com", // Custom extension download URL onClose: () => { console.log("Modal was closed"); }, // Callback when modal is closed }); await reclaimProofRequest.triggerReclaimFlow(); ``` ### Benefits of the New Flow: 1. **Platform Adaptive**: Automatically chooses the best verification method for each platform 2. **Embeddable**: Embed the portal directly in your page via iframe with `{ target: element }` 3. **Controllable**: Returns a `FlowHandle` to close the flow or access the iframe at any time 4. **Extension Support**: Leverages browser extension for desktop users when available 5. **Mobile Optimized**: Verifier app experiences on mobile devices ## Step 6: Run your application Start your development server: ```bash npm start ``` Your Reclaim SDK demo should now be running. Click the "Create Claim" button to generate a QR code. Scan this code to start the verification process. ## Understanding the Claim Process 1. **Creating a Claim**: When you click "Create Claim", the SDK generates a unique request for verification. 2. **QR Code**: The QR code contains the request URL. When scanned, it initiates the verification process. 3. **Status URL**: This URL (logged to the console) can be used to check the status of the claim process. It's useful for tracking the progress of verification. 4. **Verification**: The `onSuccess` is called when verification is successful, providing the proof data. When using a custom callback url, the proof is returned to the callback url and we get an empty array instead of a proof. 5. **Handling Failures**: The `onError` is called if verification fails, allowing you to handle errors gracefully. ## Advanced Configuration The Reclaim SDK offers several advanced options to customize your integration: 1. **Adding Context**: You can add context to your proof request, which can be useful for providing additional information: ```javascript reclaimProofRequest.setContext("0x00000000000", "Example context message"); // deprecated method: use setContext instead reclaimProofRequest.addContext("0x00000000000", "Example context message"); ``` 2. **Setting Parameters**: If your provider requires specific parameters, you can set them like this: ```javascript reclaimProofRequest.setParams({ email: "test@example.com", userName: "testUser" }); ``` 3. **Custom Redirect URL**: Set a custom URL to redirect users after the verification process. ```javascript reclaimProofRequest.setRedirectUrl("https://example.com/redirect"); ``` Redirection with body: - **url**: The URL where users should be redirected after successful proof generation. - **method** (optional): The redirection method to use. Allowed options: `GET` (default) and `POST`. *Note: `POST` form redirection is only supported in Portal flow.* - **body** (optional): List of name-value pairs to be sent as the body of the form request. - When `method` is `POST`, `body` is sent with `application/x-www-form-urlencoded` content type. - When `method` is `GET`, if `body` is set, it is sent as query parameters. *Note: Sending `body` on redirection is only supported in Portal flow.* ```javascript reclaimProofRequest.setRedirectUrl( "https://example.com/redirect", "POST", // Portal flow only [{ name: "foo", value: "bar" }] // Portal flow only ); ``` 4. **Custom Cancel Redirect URL**: Set a custom URL to redirect users on a cancellation which aborts the verification process. ```javascript reclaimProofRequest.setCancelRedirectUrl("https://example.com/error-redirect"); ``` Redirection with body: - **url**: The URL where users should be redirected after an error which aborts the verification process. - **method** (optional): The redirection method to use. Allowed options: `GET` (default) and `POST`. *Note: `POST` form redirection is only supported in Portal flow.* - **body** (optional): List of name-value pairs to be sent as the body of the form request. - When `method` is `POST`, `body` is sent with `application/x-www-form-urlencoded` content type. - When `method` is `GET`, if `body` is set, it is sent as query parameters. *Note: Sending `body` on redirection is only supported in Portal flow.* ```javascript reclaimProofRequest.setCancelRedirectUrl( "https://example.com/error-redirect", "POST", // Portal flow only [{ name: "error_code", value: "1001" }] // Portal flow only ); ``` 5. **Custom Callback URL**: Set a custom callback URL for your app which allows you to receive proofs and status updates on your callback URL: **Note**: When a custom callback URL is set, proofs are sent to the custom URL *instead* of the Reclaim backend. Consequently, the `onSuccess` callback will be invoked with an empty array (`[]`) instead of the proof data. By default, proofs are sent as HTTP `POST` with `Content-Type` as `application/x-www-form-urlencoded`. Pass function argument `jsonProofResponse` as `true` to send proofs with `Content-Type` as `application/json`. ```javascript reclaimProofRequest.setAppCallbackUrl("https://example.com/callback", true); ``` This verification session's id will also be present in `X-Reclaim-Session-Id` header of the request. The request URL will contain query param `allowAiWitness` with value `true` when AI Witness should be allowed by handler of the request. 6. **Custom Error Callback URL**: Set a custom cancel callback URL for your app which allows you to receive user- or provider-initiated cancellation on your callback URL: ```javascript reclaimProofRequest.setCancelCallbackUrl("https://example.com/error-callback"); ``` When verificaiton is cancelled by user (or upon error when auto-submit is enabled), following data is sent as an HTTP POST request to the url with `Content-Type: application/json`: ```json { "type": "string", // Name of the exception "message": "string", "sessionId": "string", // context as canonicalized json string "context": "string", // Other fields with more details about error may be present // [key: any]: any } ``` This verification session's id will also be present in `X-Reclaim-Session-Id` header of the request. For more details about response format, check out [official documentation of Error Callback URL](https://docs.reclaimprotocol.org/js-sdk/preparing-request#cancel-callback). 7. **Modal Customization for Desktop Users**: Customize the appearance and behavior of the QR code modal shown to desktop users: ```javascript reclaimProofRequest.setModalOptions({ title: "Verify Your Account", description: "Scan the QR code with your mobile device or install our browser extension", darkTheme: false, // Enable dark theme (default: false) extensionUrl: "https://chrome.google.com/webstore/detail/reclaim", // Custom extension URL }); ``` 8. **Browser Extension Configuration**: Configure browser extension behavior and detection: ```javascript // Check if browser extension is available const isExtensionAvailable = await reclaimProofRequest.isBrowserExtensionAvailable(); // Trigger the verification flow with automatic platform detection await reclaimProofRequest.triggerReclaimFlow(); // Initialize with browser extension options const proofRequest = await ReclaimProofRequest.init(APP_ID, APP_SECRET, PROVIDER_ID, { useBrowserExtension: true, // Enable browser extension support (default: true) extensionID: "custom-extension-id", // Custom extension identifier useAppClip: false, // Enable mobile app clips (default: false) log: true, // Enable troubleshooting mode and more verbose logging for debugging }); ``` 9. **Custom Portal URL and App Clip URLs**: You can customize the portal/share page and app clip URLs for your app: ```javascript const proofRequest = await ReclaimProofRequest.init(APP_ID, APP_SECRET, PROVIDER_ID, { portalUrl: "https://your-custom-domain.com/verify", // Custom URL for both portal and app modes customAppClipUrl: "https://appclip.apple.com/id?p=your.custom.app.clip", // Custom iOS App Clip URL // ... other options }); ``` > **Defaults:** > - Portal mode: `https://portal.reclaimprotocol.org` — remote browser verification > - App mode: `https://share.reclaimprotocol.org` — redirects to the verifier app > > Setting `portalUrl` overrides both modes. The previous `customSharePageUrl` is deprecated but still supported — if both are provided, `portalUrl` takes precedence. `useAppClip` defaults to `false`. 10. **Platform-Specific Flow Control**: Both `triggerReclaimFlow()` and `getRequestUrl()` support `verificationMode`: ```javascript // Portal flow (default) — opens in new tab const handle = await reclaimProofRequest.triggerReclaimFlow(); handle.tab; // Window reference handle.close(); // close tab, stop polling // Embedded portal — loads inside a DOM element const handle = await reclaimProofRequest.triggerReclaimFlow({ target: document.getElementById('container') }); handle.iframe; // HTMLIFrameElement reference handle.close(); // remove iframe, stop polling // Verifier app flow const handle = await reclaimProofRequest.triggerReclaimFlow({ verificationMode: 'app' }); handle.close(); // stop polling // getRequestUrl also supports verificationMode const portalUrl = await reclaimProofRequest.getRequestUrl(); const appUrl = await reclaimProofRequest.getRequestUrl({ verificationMode: 'app' }); ``` 11. **Exporting and Importing SDK Configuration**: You can export the entire Reclaim SDK configuration as a JSON string and use it to initialize the SDK with the same configuration on a different service or backend: ```javascript // On the client-side or initial service const configJson = reclaimProofRequest.toJsonString(); console.log("Exportable config:", configJson); // Send this configJson to your backend or another service // On the backend or different service const importedRequest = ReclaimProofRequest.fromJsonString(configJson); const requestUrl = await importedRequest.getRequestUrl(); ``` This allows you to generate request URLs and other details from your backend or a different service while maintaining the same configuration. 12. **Utility Methods**: Additional utility methods for managing your proof requests: ```javascript // Get the current session ID const sessionId = reclaimProofRequest.getSessionId(); console.log("Current session ID:", sessionId); ``` 13. **Control auto-submission of proofs**: Whether the verification client should automatically submit necessary proofs once they are generated. If set to false, the user must manually click a button to submit. Defaults to true. ```js // Initialize with options const proofRequest = await ReclaimProofRequest.init(APP_ID, APP_SECRET, PROVIDER_ID, { canAutoSubmit: true, }); ``` 14. **Add additional metadata for verification client**: Additional metadata to pass to the verification client. This can be used to customize the client experience, such as customizing themes or UI by passing context-specific information. The keys and values must be strings. For most clients, this is not required and goes unused. This has no effect on the verification process. ```js // Initialize with options const proofRequest = await ReclaimProofRequest.init(APP_ID, APP_SECRET, PROVIDER_ID, { metadata: { theme: 'dark', verify_another_way_link: 'https://exampe.org/alternative-verification?id=1234' }, }); ``` 15. Set preferred locale for verification client: An identifier used to select a user's language and formatting preferences. This represents a Unicode Language Identifier (i.e. without Locale extensions), except variants are not supported. Locales are expected to be canonicalized according to the "preferred value" entries in the [IANA Language Subtag Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry). For example, `he`, and `iw` are equal and both have the languageCode `he`, because `iw` is a deprecated language subtag that was replaced by the subtag `he`. Defaults to the browser's locale if available, otherwise English (en). ```js // Initialize with options const proofRequest = await ReclaimProofRequest.init(APP_ID, APP_SECRET, PROVIDER_ID, { preferredLocale: 'en-US', }); ``` ## Handling Proofs on Your Backend For production applications, it's recommended to handle proofs, and cancellations on your backend: 1. Set a callback URL: ```javascript reclaimProofRequest.setAppCallbackUrl("https://your-backend.com/receive-proofs"); ``` 2. Set a cancel callback URL: ```javascript reclaimProofRequest.setCancelCallbackUrl("https://your-backend.com/receive-cancel"); ``` These options allow you to securely process proofs or cancellations on your server. > [!TIP] > **Best Practice:** When using `setAppCallbackUrl` and/or `setCancelCallbackUrl`, your backend receives the proof or cancellation details directly. We recommend your backend notifies the frontend (e.g. via WebSockets, SSE, or polling) to stop the verification process and handle the appropriate success/failure action. Do not rely completely on `startSession` callbacks on the frontend when using these backend callbacks. ## Proof Verification The SDK provides a `verifyProof` function to manually verify proofs cryptographically on your backend. This involves validating the attestor signatures and enforcing that the proof matches the expected requirements. Here are the possible ways to use `verifyProof`, from beginner to advanced: ### 1. Simple Verification The easiest way is to use `request.getProviderVersion()` which returns the provider ID and exact version used in the session — pass it directly as the config: ```javascript import { verifyProof } from "@reclaimprotocol/js-sdk"; // Using getProviderVersion() — recommended const providerVersion = reclaimProofRequest.getProviderVersion(); const { isVerified, data, error } = await verifyProof(proof, providerVersion); if (isVerified) { console.log("Proof is valid"); console.log("Context:", data[0].context); console.log("Extracted parameters:", data[0].extractedParameters); } else { console.log("Proof is invalid, reason:", error); } ``` Or, by manually providing the provider details: ```javascript const { isVerified, data } = await verifyProof(proof, { providerId: "YOUR_PROVIDER_ID", // The exact provider version used in the session. providerVersion: "1.0.0", // Optionally provide tags. For example, this can be `['ai']` when you want to allow patches from ai. allowedTags: ["ai"] }); ``` Or, with a known hash: ```javascript const { isVerified, data, error } = await verifyProof(proof, { hashes: ['0xAbC...'] }); ``` ### 2. Intermediate: Strict Hash Verification If you want to avoid network requests, you can manually feed the expected cryptographic hashes your system allows. ```javascript // Verify a proof against a known, strict expected hash const { isVerified, data } = await verifyProof(proof, { hashes: ['0x1abc2def3456...'] }); ``` ### 3. Advanced: Multiple Proofs and Optional Matches When building advanced use-cases, you might process multiple distinct proofs at once or deal with providers that yield a few valid hash possibilities (e.g., due to optional data fields). ```javascript const result = await verifyProof([proof1, proof2, sameAsProof2], { hashes: [ // A string hash is equivalent to an object with { value: '...', required: true, multiple: true }. '0xStrictHash123...', { // An array 'value' means that 1 proof can have any 1 matching hash // from this list, typically because of optional variables in the original request. value: ['0xOptHash1...', '0xOptHashA...'], // 'multiple' being true (which is the default) means any proof matching this hash // is allowed to appear multiple times in the list of proofs you are verifying. multiple: true }, { value: '0xE33...', // 'required: false' means there can be 0 proofs matching this hash. // Such proofs may be optionally present in the list of proofs. // (By default, 'required' is true). required: false } ] }); if (result.isVerified) { result.data.forEach((d, i) => { console.log(`Proof ${i + 1} context:`, d.context); console.log(`Proof ${i + 1} params:`, d.extractedParameters); }); } ``` ### 4. Danger Zone: Disabled Content Validation If you only want to verify the attestor signature but wish to dangerously bypass the parameter/content match (Not Recommended): ```javascript const { isVerified } = await verifyProof(proof, { dangerouslyDisableContentValidation: true }); ``` ### Replay Protection Proofs are submitted by the user, so the server must not trust any field on the proof in isolation. `verifyProof` (and `verifyTeeAttestation`) cryptographically bind a proof to a specific session, but **the SDK is stateless** — it cannot tell whether the same valid proof has already been verified. Preventing replay is the caller's responsibility. **What the SDK guarantees** The `attestationNonce` baked into the proof context is computed as `keccak256("RECLAIM_TEE_NONCE_V1" : applicationId : sessionId : timestamp : appSecret)`. When you call `verifyProof` with `teeAttestation: { appSecret }`, the SDK: - Recomputes the nonce from `(applicationId, sessionId, timestamp)` in the proof context using **your** `appSecret` and asserts it matches — only the holder of the app secret can mint a valid nonce, so the user cannot rebind a proof to a different `sessionId` or `applicationId`. - Confirms the recomputed nonce appears in the TEE attestation token's `eat_nonce` (signed by Google's Confidential Computing OIDC issuer), so the TEE-signed binding cannot be forged either. - Asserts the `applicationId` in the nonce data matches the address derived from `appSecret`, and that `sessionId` in the nonce data agrees with `reclaimSessionId` in `claimData.context` and `proxySessionId`/`sessionId` in `claimData.parameters`. - Asserts `claimData.timestampS` is within 10 minutes of the nonce timestamp. In short: the user cannot mutate `sessionId`, `applicationId`, or `timestamp` on a proof without invalidating it. They *can*, however, resubmit the same valid proof. **What you must do on your server** 1. **Use a session you initiated.** Call `ReclaimProofRequest.init(...)` and `getSessionId()` server-side (or record the `sessionId` returned to your callback). When verifying, reject any proof whose `sessionId` you did not issue. 2. **Dedupe by `sessionId`.** Persist accepted `sessionId`s (e.g., a unique index in your DB or a TTL cache) and reject a `sessionId` that has already been verified. This is the primary replay defense. 3. **Require `teeAttestation`** in `verifyProof` for any flow where replay or tampering matters — without it, the cryptographic binding to `appSecret` is not checked. 4. **Enforce a freshness window.** Reject proofs whose `claimData.timestampS` is older than your business policy allows (the SDK only enforces a 10-minute skew between the nonce timestamp and claim timestamp, not absolute freshness). ```javascript // Pseudocode for a server-side verification handler const expectedSessionId = await loadSessionForUser(req.user); // session you created const sessionId = JSON.parse(proof.claimData.context).reclaimSessionId; if (sessionId !== expectedSessionId) { throw new Error("session mismatch"); } if (await db.consumedSessions.has(sessionId)) { throw new Error("proof already used"); // replay } const { isVerified, error } = await verifyProof(proof, { ...providerVersion, teeAttestation: { appSecret: process.env.APP_SECRET }, }); if (!isVerified) throw error; await db.consumedSessions.add(sessionId); // mark consumed atomically ``` > [!WARNING] > Without server-side session tracking and dedup, a malicious user can submit the same valid proof repeatedly. The cryptographic checks in `verifyProof` are necessary but not sufficient for replay protection. ## TEE Attestation Verification The SDK supports verifying TEE (Trusted Execution Environment) attestations included in proofs. The attestation flow verifies the Google Confidential Computing OIDC token returned by Popcorn's GCP attestor and checks that it is bound to the proof nonce, application identity, and image digests. ### Requesting TEE Attestation TEE attestation is requested by default during proof generation: ```javascript const proofRequest = await ReclaimProofRequest.init(APP_ID, APP_SECRET, PROVIDER_ID); ``` The request includes the SDK's attestation version so the attestor can continue serving older SDK clients when newer attestation formats are introduced. To opt out, set `acceptTeeAttestation: false` during initialization. ### Verifying TEE Attestation via `verifyProof` Provide a `teeAttestation` config to require and verify TEE attestation. The application ID is derived automatically from `appSecret`. If TEE data is missing or invalid, verification will fail with a `TeeVerificationError`. ```javascript import { verifyProof, TeeVerificationError } from "@reclaimprotocol/js-sdk"; const { isVerified, isTeeAttestationVerified, data, error } = await verifyProof(proof, { hashes: ['0xAbC...'], teeAttestation: { appSecret: APP_SECRET, }, }); if (isVerified) { console.log("Proof verified with hardware attestation"); console.log("TEE verified:", isTeeAttestationVerified); // always true when teeAttestation is provided and passes console.log("Extracted parameters:", data[0].extractedParameters); } else if (error instanceof TeeVerificationError) { console.log("TEE verification failed:", error.message); } else { console.log("Proof verification failed:", error); } ``` The result includes `isTeeAttestationVerified`. It is `true` when `teeAttestation` was provided and verification passed, and `undefined` when `teeAttestation` was not requested. ### What TEE verification checks - **Application binding**: Derives the application ID from `appSecret` and confirms the attestation was generated for your application - **Nonce binding**: Ensures the attestation nonce matches the proof context - **Session and timestamp binding**: Verifies the nonce metadata matches the proof session and is within the allowed skew - **OIDC token signature**: Validates the Google Confidential Computing attestation JWT against Google's JWKS (responses are cached for 5 minutes) - **Platform claims**: Confirms the expected GCP confidential-computing claims (issuer, secure boot, hardware model, GCE instance metadata) - **Digest binding**: Confirms the workload and verifier image digests are present in the attestation token nonce list ### Standalone `verifyTeeAttestation` You can verify TEE attestation for a single proof without running full proof verification: ```javascript import { verifyTeeAttestation } from "@reclaimprotocol/js-sdk"; const { isVerified, error } = await verifyTeeAttestation(proof, APP_SECRET); if (isVerified) { console.log("TEE attestation verified"); } else { console.log("TEE verification failed:", error); } ``` `appSecret` is your Reclaim application secret. The application ID is derived from it automatically. ### Browser vs Server Verification TEE verification fetches Google's OIDC metadata and JWKS endpoints. In browser-only environments this may fail due to cross-origin restrictions. For web apps, verify TEE attestations on the server: ```javascript const response = await fetch('/api/verify-tee', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ proofs }), }); const { results } = await response.json(); console.log(results); ``` The server route should read the app secret from environment variables only (never accept it from the client request body). See the example app for a reference implementation: - `example/src/app/api/verify-tee/route.ts` ## Error Handling The SDK provides specific error types for different failure scenarios. Here's how to handle them: ```javascript import { ReclaimProofRequest } from "@reclaimprotocol/js-sdk"; try { const proofRequest = await ReclaimProofRequest.init(APP_ID, APP_SECRET, PROVIDER_ID); await proofRequest.startSession({ onSuccess: (proofs) => { // proofs can be empty if callback url set console.log("Proof received:", proofs); }, onError: (error) => { // Handle different error types if (error.name === "ProofNotVerifiedError") { console.error("Proof verification failed"); } else if (error.name === "ProviderFailedError") { console.error("Provider failed to generate proof"); } else if (error.name === "SessionNotStartedError") { console.error("Session could not be started"); } else { console.error("Unknown error:", error.message); } }, }); } catch (error) { // Handle initialization errors if (error.name === "InitError") { console.error("Failed to initialize SDK:", error.message); } else if (error.name === "InvalidParamError") { console.error("Invalid parameters provided:", error.message); } } ``` **Common Error Types:** - `InitError`: SDK initialization failed - `InvalidParamError`: Invalid parameters provided - `SignatureNotFoundError`: Missing or invalid signature - `ProofNotVerifiedError`: Proof verification failed - `ProviderFailedError`: Provider failed to generate proof - `SessionNotStartedError`: Session could not be started - `ProofSubmissionFailedError`: Proof submission to callback failed - `ErrorDuringVerificationError`: An abort error during verification which was caused by the user aborting the verification process or provider's JS script raising a validation error ## Example Repos - [Reclaim Demo Website](https://github.com/reclaimprotocol/reclaim-demo-website-v3) ## Next Steps Explore the [Reclaim Protocol documentation](https://docs.reclaimprotocol.org/) for more advanced features and best practices for integrating the SDK into your production applications. Happy coding with Reclaim Protocol! ## Contributing to Our Project We welcome contributions to our project! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request. ## Security Note Always keep your Application Secret secure. Never expose it in client-side code or public repositories. ## Code of Conduct Please read and follow our [Code of Conduct](https://github.com/reclaimprotocol/.github/blob/main/Code-of-Conduct.md) to ensure a positive and inclusive environment for all contributors. ## Security If you discover any security-related issues, please refer to our [Security Policy](https://github.com/reclaimprotocol/.github/blob/main/SECURITY.md) for information on how to responsibly disclose vulnerabilities. ## Contributor License Agreement Before contributing to this project, please read and sign our [Contributor License Agreement (CLA)](https://github.com/reclaimprotocol/.github/blob/main/CLA.md). ## Indie Hackers For Indie Hackers: [Check out our guidelines and potential grant opportunities](https://github.com/reclaimprotocol/.github/blob/main/Indie-Hackers.md) ## License This project is licensed under a [custom license](https://github.com/reclaimprotocol/.github/blob/main/LICENSE). By contributing to this project, you agree that your contributions will be licensed under its terms. Thank you for your contributions!