@sparkvault/sdk 1.0.0 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SparkVault JavaScript SDK
2
2
 
3
- A unified JavaScript SDK for SparkVault services: Identity verification, encrypted Sparks, secure Vaults, and cryptographic RNG.
3
+ The official SparkVault JavaScript SDK for browser applications. Add passwordless authentication to your app in minutes with zero-config integration or full programmatic control.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@sparkvault/sdk.svg)](https://www.npmjs.com/package/@sparkvault/sdk)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -8,627 +8,699 @@ A unified JavaScript SDK for SparkVault services: Identity verification, encrypt
8
8
  ## Table of Contents
9
9
 
10
10
  - [Installation](#installation)
11
- - [Quick Start](#quick-start)
12
- - [Architecture](#architecture)
13
- - [Modules](#modules)
14
- - [Identity](#identity)
15
- - [Sparks](#sparks)
16
- - [Vaults](#vaults)
17
- - [RNG](#rng)
18
- - [Configuration](#configuration)
11
+ - [Zero-Config Integration](#zero-config-integration)
12
+ - [Manual Initialization](#manual-initialization)
13
+ - [Identity Verification](#identity-verification)
14
+ - [Backend Token Verification](#backend-token-verification)
15
+ - [React Integration](#react-integration)
19
16
  - [Error Handling](#error-handling)
20
- - [TypeScript](#typescript)
21
- - [Security](#security)
17
+ - [TypeScript Support](#typescript-support)
18
+ - [Browser Support](#browser-support)
22
19
  - [Development](#development)
23
20
  - [License](#license)
24
21
 
25
22
  ## Installation
26
23
 
27
- ### CDN (Browser)
24
+ ### CDN (Recommended)
28
25
 
29
- Include the SDK directly in your HTML. This is the recommended approach for most web applications.
26
+ ```html
27
+ <script src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"></script>
28
+ ```
30
29
 
31
- #### Zero-Config Auto-Init (Recommended)
30
+ ### npm / yarn
32
31
 
33
- The simplest way to add SparkVault authentication to your site:
32
+ ```bash
33
+ npm install @sparkvault/sdk
34
+ # or
35
+ yarn add @sparkvault/sdk
36
+ ```
37
+
38
+ **Bundle Formats:**
39
+ - `sparkvault.js` — UMD bundle for browsers (recommended)
40
+ - `sparkvault.esm.js` — ES modules for modern bundlers
41
+ - `sparkvault.cjs.js` — CommonJS for Node.js
42
+
43
+ ## Zero-Config Integration
44
+
45
+ Add SparkVault authentication to your site with a single script tag — no JavaScript required!
34
46
 
35
47
  ```html
48
+ <!-- Add this single script tag -->
36
49
  <script
37
50
  async
38
51
  src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"
39
- data-account-id="acc_your_account"
40
- data-attach-selector=".js-sparkvault-auth"
41
- data-success-url="https://example.com/auth/verify-token"
42
- data-debug="true"
52
+ data-account-id="acc_YOUR_ACCOUNT_ID"
53
+ data-attach-selector=".login-btn"
54
+ data-success-url="/auth/verify-token"
43
55
  ></script>
44
56
 
45
- <button class="js-sparkvault-auth">Login with SparkVault</button>
57
+ <!-- Any element matching the selector becomes a login button -->
58
+ <button class="login-btn">Sign In</button>
59
+ <a href="#" class="login-btn">Create Account</a>
46
60
  ```
47
61
 
48
62
  That's it! The SDK automatically:
49
63
  - Initializes with your account ID
50
- - Attaches click handlers to matching elements
51
- - Opens the verification modal on click
64
+ - Attaches click handlers to all matching elements
65
+ - Opens the verification modal when clicked
52
66
  - POSTs the result to your success URL
67
+ - Watches for dynamically added elements (MutationObserver)
53
68
 
54
- #### Auto-Init Data Attributes
55
-
56
- | Attribute | Description |
57
- |-----------|-------------|
58
- | `data-account-id` | Your SparkVault account ID (required for auto-init) |
59
- | `data-attach-selector` | CSS selector for elements to attach click handlers |
60
- | `data-success-url` | URL to POST `{ token, identity, identityType }` on success |
61
- | `data-success-function` | Global function name to call on success (e.g., `"handleAuth"` or `"MyApp.auth.onSuccess"`) |
62
- | `data-error-url` | URL to redirect to on error (appends `?error=message`) |
63
- | `data-error-function` | Global function name to call on error |
64
- | `data-debug` | Set to `"true"` for verbose console logging |
69
+ ### Data Attributes
65
70
 
66
- #### Success Handling
71
+ | Attribute | Required | Description |
72
+ |-----------|----------|-------------|
73
+ | `data-account-id` | Yes | Your SparkVault account ID (required for auto-init) |
74
+ | `data-attach-selector` | No | CSS selector for elements to attach click handlers |
75
+ | `data-success-url` | No | URL to POST `{ token, identity, identityType }` on success |
76
+ | `data-success-function` | No | Global function name to call on success (e.g., `"MyApp.auth.onSuccess"`) |
77
+ | `data-error-url` | No | URL to redirect on error (appends `?error=message`) |
78
+ | `data-error-function` | No | Global function name to call on error |
79
+ | `data-preload-config` | No | Set to `"false"` to defer config loading. Default: true |
80
+ | `data-timeout` | No | Request timeout in milliseconds. Default: 30000 |
81
+ | `data-debug` | No | Set to `"true"` for verbose console logging |
67
82
 
68
- When verification succeeds, the SDK can call a function and/or POST to a URL:
83
+ ### Complete Example
69
84
 
70
85
  ```html
71
- <!-- Call a function -->
86
+ <script
87
+ async
88
+ src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"
89
+ data-account-id="acc_YOUR_ACCOUNT_ID"
90
+ data-attach-selector=".js-sparkvault-auth"
91
+ data-success-url="https://example.com/auth/verify-token"
92
+ data-success-function="onSparkVaultSuccess"
93
+ data-error-function="onSparkVaultError"
94
+ data-debug="true"
95
+ ></script>
96
+
72
97
  <script>
73
- function handleSparkVaultAuth(result) {
98
+ // Optional: Define callback functions
99
+ function onSparkVaultSuccess(result) {
74
100
  console.log('Verified:', result.identity);
75
101
  console.log('Token:', result.token);
76
- // Your logic here...
102
+ // The SDK will also POST to data-success-url
103
+ }
104
+
105
+ function onSparkVaultError(error) {
106
+ console.error('Error:', error.message);
107
+ alert('Authentication failed. Please try again.');
77
108
  }
78
109
  </script>
79
- <script
80
- src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"
81
- data-account-id="acc_your_account"
82
- data-attach-selector=".login-btn"
83
- data-success-function="handleSparkVaultAuth"
84
- ></script>
85
110
 
86
- <!-- POST to your backend -->
87
- <script
88
- src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"
89
- data-account-id="acc_your_account"
90
- data-attach-selector=".login-btn"
91
- data-success-url="/auth/verify-sparkvault-token"
92
- ></script>
111
+ <!-- Multiple elements can use the same selector -->
112
+ <button class="js-sparkvault-auth">Sign In</button>
113
+ <a href="#" class="js-sparkvault-auth">Login with SparkVault</a>
93
114
  ```
94
115
 
95
- When POSTing to `data-success-url`, the SDK sends:
116
+ ### Success URL Response
117
+
118
+ When you specify `data-success-url`, the SDK POSTs the verification result:
119
+
96
120
  ```json
97
121
  {
98
- "token": "eyJ...",
122
+ "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
99
123
  "identity": "user@example.com",
100
124
  "identityType": "email"
101
125
  }
102
126
  ```
103
127
 
104
- Your backend can respond with:
105
- ```json
106
- { "redirectUrl": "/dashboard" } // SDK redirects to this URL
107
- { "reload": true } // SDK reloads the page
108
- ```
128
+ Your backend can respond with redirect instructions:
109
129
 
110
- #### Manual Initialization
130
+ ```javascript
131
+ // Option 1: Redirect to a URL
132
+ { "redirectUrl": "/dashboard" }
133
+ // or
134
+ { "redirect_url": "/dashboard" }
111
135
 
112
- For more control, initialize the SDK manually:
136
+ // Option 2: Reload the current page
137
+ { "reload": true }
113
138
 
114
- ```html
115
- <script src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"></script>
116
- <script>
117
- const sv = SparkVault.init({
118
- accountId: 'acc_your_account'
119
- });
120
-
121
- // Trigger verification popup manually
122
- document.getElementById('login-btn').addEventListener('click', async () => {
123
- const result = await sv.identity.pop();
124
- // Handle result...
125
- });
126
- </script>
139
+ // Option 3: Return data (no automatic action)
140
+ { "success": true, "user": { "id": 123, "email": "user@example.com" } }
127
141
  ```
128
142
 
129
- #### Available CDN Files
143
+ ### Manual Trigger from JavaScript
130
144
 
131
- | File | Description |
132
- |------|-------------|
133
- | `sparkvault.js` | Minified UMD bundle (recommended) |
134
- | `sparkvault.esm.js` | ES module bundle |
135
- | `sparkvault.cjs.js` | CommonJS bundle |
145
+ You can also trigger authentication programmatically using the `SparkVault` global:
136
146
 
137
- ### npm
147
+ ```javascript
148
+ // Trigger authentication from anywhere in your code
149
+ SparkVault.identity.pop();
138
150
 
139
- ```bash
140
- npm install @sparkvault/sdk
151
+ // This uses your configured success/error handlers
141
152
  ```
142
153
 
143
- ```javascript
144
- // ES Modules
145
- import { SparkVault } from '@sparkvault/sdk';
146
-
147
- // CommonJS
148
- const { SparkVault } = require('@sparkvault/sdk');
154
+ ```html
155
+ <button onclick="SparkVault.identity.pop()">
156
+ Sign In with SparkVault
157
+ </button>
149
158
  ```
150
159
 
151
- ## Quick Start
160
+ ## Manual Initialization
161
+
162
+ For full control over the SDK, initialize it manually in JavaScript:
152
163
 
153
164
  ```javascript
154
- // Initialize the SDK
155
- const sv = SparkVault.init({
156
- accountId: 'acc_your_account'
157
- });
165
+ import SparkVault from '@sparkvault/sdk';
158
166
 
159
- // Identity - Open verification popup
160
- const result = await sv.identity.pop({
161
- email: 'user@example.com'
167
+ const sparkvault = SparkVault.init({
168
+ accountId: 'acc_YOUR_ACCOUNT_ID' // Required
162
169
  });
163
170
 
164
- // The token is a single-use verification proof - send it to YOUR backend
165
- await fetch('/api/auth/login', {
166
- method: 'POST',
167
- headers: { 'Content-Type': 'application/json' },
168
- body: JSON.stringify({ token: result.token })
169
- });
170
- // Your backend verifies the token, then creates YOUR session
171
+ // The SDK is now ready to use
172
+ const result = await sparkvault.identity.pop();
171
173
  ```
172
174
 
173
- ## Architecture
174
-
175
- The SparkVault SDK follows a Stripe.js-like architecture:
176
-
177
- ```
178
- ┌─────────────────────────────────────────────────────────────┐
179
- │ Your Application │
180
- ├─────────────────────────────────────────────────────────────┤
181
- │ SparkVault SDK │
182
- │ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │
183
- │ │ Identity │ Sparks │ Vaults │ RNG │ │
184
- │ │ Module │ Module │ Module │ Module │ │
185
- │ └─────────────┴─────────────┴─────────────┴─────────────┘ │
186
- │ │ │
187
- │ HTTP Client │
188
- ├─────────────────────────────────────────────────────────────┤
189
- │ api.sparkvault.com │
190
- └─────────────────────────────────────────────────────────────┘
191
- ```
175
+ ### Configuration Options
192
176
 
193
- ### Identity Popup Flow
177
+ | Option | Type | Default | Description |
178
+ |--------|------|---------|-------------|
179
+ | `accountId` | `string` | **required** | Your SparkVault account ID (starts with `acc_`) |
180
+ | `timeout` | `number` | `30000` | HTTP request timeout in milliseconds |
181
+ | `preloadConfig` | `boolean` | `true` | Preload Identity config for instant modal |
194
182
 
195
- The Identity module uses a secure popup window for authentication:
183
+ ### CDN Usage
196
184
 
197
- 1. **SDK opens popup** → `api.sparkvault.com/apps/identity/{account_id}/embed`
198
- 2. **User authenticates** → Passkey, TOTP, Magic Link, or Social OAuth
199
- 3. **Popup posts result** → `postMessage` with signed JWT token
200
- 4. **SDK validates origin** → Ensures message is from SparkVault
201
- 5. **Promise resolves** → Returns verified user info and token
185
+ ```html
186
+ <script src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"></script>
187
+ <script>
188
+ const sparkvault = SparkVault.init({
189
+ accountId: 'acc_YOUR_ACCOUNT_ID'
190
+ });
202
191
 
192
+ document.getElementById('login-btn').addEventListener('click', async () => {
193
+ const result = await sparkvault.identity.pop();
194
+ console.log('Verified:', result.identity);
195
+ });
196
+ </script>
203
197
  ```
204
- ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
205
- │ Your App │ │ SDK Popup │ │ SparkVault │
206
- │ │ │ │ │ Identity │
207
- ├──────────────┤ ├──────────────────┤ ├──────────────┤
208
- │ sv.identity │────>│ Opens popup │ │ │
209
- │ .pop() │ │ window │────>│ /embed page │
210
- │ │ │ │ │ │
211
- │ │ │ User selects │ │ Auth methods │
212
- │ │ │ auth method │<────│ displayed │
213
- │ │ │ │ │ │
214
- │ │ │ User completes │────>│ Verify │
215
- │ │ │ authentication │ │ credentials │
216
- │ │ │ │ │ │
217
- │ Promise │<────│ postMessage │<────│ Sign JWT │
218
- │ resolves │ │ with token │ │ token │
219
- └──────────────┘ └──────────────────┘ └──────────────┘
220
- ```
221
-
222
- ## Modules
223
198
 
224
- ### Identity
199
+ ## Identity Verification
225
200
 
226
- Verify user identity through passkey, TOTP, magic link, or social authentication.
201
+ The Identity module provides passwordless authentication through a beautiful, customizable modal. Users can verify via passkeys, email codes, SMS codes, magic links, or social providers.
227
202
 
228
- The SDK provides two methods:
229
- - **`pop()`** — Opens a modal popup (most common)
230
- - **`render()`** — Embeds UI inline in your page
203
+ ### Basic Usage
231
204
 
232
205
  ```javascript
233
- // Open popup modal
234
- const result = await sv.identity.pop();
235
-
236
- // With options
237
- const result = await sv.identity.pop({
238
- email: 'user@example.com', // Pre-fill email
239
- methods: ['passkey', 'totp_email', 'magic_link'], // Filter methods
240
- theme: 'dark', // 'light', 'dark', or 'auto' (system preference)
241
- backdropBlur: true, // Enable backdrop blur effect (default: true)
242
- locale: 'en', // UI language
243
- onSuccess: (result) => {}, // Success callback
244
- onCancel: () => {}, // User cancelled
245
- onError: (error) => {} // Error callback
246
- });
247
-
248
- // Result structure
249
- console.log(result.token); // Signed JWT
250
- console.log(result.identity); // Verified identity (email or phone)
251
- console.log(result.identityType); // 'email' or 'phone'
252
- ```
206
+ // Open the verification modal
207
+ const result = await sparkvault.identity.pop();
253
208
 
254
- #### Inline Rendering
209
+ // User verified!
210
+ console.log(result.identity); // "user@example.com" or "+14155551234"
211
+ console.log(result.identityType); // "email" or "phone"
212
+ console.log(result.token); // Signed JWT token
255
213
 
256
- For cases where you want to embed the authentication UI directly in your page instead of a popup modal, use `render()`:
257
-
258
- ```javascript
259
- // Render inline in a container element
260
- const result = await sv.identity.render({
261
- target: document.getElementById('auth-container'),
262
- email: 'user@example.com'
263
- });
264
-
265
- // Embed in your own dialog without SparkVault header/footer
266
- const result = await sv.identity.render({
267
- target: dialogContentElement,
268
- showHeader: false,
269
- showFooter: false
270
- });
271
-
272
- // Minimal - just the auth flow, no chrome
273
- const result = await sv.identity.render({
274
- target: myElement,
275
- showHeader: false,
276
- showCloseButton: false,
277
- showFooter: false
214
+ // Send the token to your backend for verification
215
+ await fetch('/api/auth/login', {
216
+ method: 'POST',
217
+ headers: { 'Content-Type': 'application/json' },
218
+ body: JSON.stringify({ token: result.token })
278
219
  });
279
220
  ```
280
221
 
281
- | Option | Type | Default | Description |
282
- |--------|------|---------|-------------|
283
- | `target` | `HTMLElement` | **required** | Element to render the auth UI into |
284
- | `showHeader` | `boolean` | `true` | Show header with branding |
285
- | `showCloseButton` | `boolean` | `true` | Show close button in header |
286
- | `showFooter` | `boolean` | `true` | Show "Secured by SparkVault" footer |
287
-
288
- All other options from `pop()` are also supported (`email`, `phone`, `onSuccess`, etc.).
222
+ ### pop() Options
289
223
 
290
- #### Supported Authentication Methods
224
+ | Option | Type | Description |
225
+ |--------|------|-------------|
226
+ | `email` | `string` | Pre-fill email address (mutually exclusive with phone) |
227
+ | `phone` | `string` | Pre-fill phone in E.164 format, e.g. `"+14155551234"` |
228
+ | `backdropBlur` | `boolean` | Override backdrop blur setting from app config |
229
+ | `onCancel` | `function` | Callback when user closes modal without verifying |
230
+ | `onSuccess` | `function` | Callback on success (before promise resolves) |
231
+ | `onError` | `function` | Callback on error (before promise rejects) |
291
232
 
292
- | Method | Description |
293
- |--------|-------------|
294
- | `passkey` | WebAuthn/FIDO2 passkey authentication |
295
- | `totp_email` | Time-based OTP sent via email |
296
- | `totp_sms` | Time-based OTP sent via SMS |
297
- | `magic_link` | One-click email verification link |
298
- | `google` | Google OAuth |
299
- | `apple` | Apple Sign In |
300
- | `microsoft` | Microsoft OAuth |
301
- | `github` | GitHub OAuth |
233
+ ### pop() Result
302
234
 
303
- #### Appearance Options
235
+ | Field | Type | Description |
236
+ |-------|------|-------------|
237
+ | `token` | `string` | Signed JWT token (Ed25519). Verify this on your backend. |
238
+ | `identity` | `string` | The verified email address or phone number |
239
+ | `identityType` | `"email" \| "phone"` | Type of identity that was verified |
304
240
 
305
- The Identity popup supports theming and visual effects:
241
+ ### Example with Options
306
242
 
307
243
  ```javascript
308
- // Dark mode
309
- await sv.identity.pop({
310
- theme: 'dark'
311
- });
312
-
313
- // Light mode
314
- await sv.identity.pop({
315
- theme: 'light'
316
- });
317
-
318
- // Auto (follows system preference)
319
- await sv.identity.pop({
320
- theme: 'auto'
321
- });
244
+ try {
245
+ const result = await sparkvault.identity.pop({
246
+ // Pre-fill the email field
247
+ email: 'user@example.com',
248
+
249
+ // Handle cancellation
250
+ onCancel: () => {
251
+ console.log('User cancelled');
252
+ }
253
+ });
322
254
 
323
- // Disable backdrop blur (enabled by default)
324
- await sv.identity.pop({
325
- backdropBlur: false
326
- });
255
+ // Success! Send token to your backend
256
+ await loginWithToken(result.token);
327
257
 
328
- // Combine options
329
- await sv.identity.pop({
330
- email: 'user@example.com',
331
- theme: 'dark',
332
- backdropBlur: true
333
- });
258
+ } catch (error) {
259
+ if (error.code === 'user_cancelled') {
260
+ // User closed the modal
261
+ } else {
262
+ // Handle other errors
263
+ console.error('Verification failed:', error.message);
264
+ }
265
+ }
334
266
  ```
335
267
 
336
- | Option | Type | Default | Description |
337
- |--------|------|---------|-------------|
338
- | `theme` | `'light' \| 'dark' \| 'auto'` | Tenant default | UI color theme. `'auto'` follows system preference. |
339
- | `backdropBlur` | `boolean` | `true` | Apply blur effect to the page behind the popup. |
268
+ ### Authentication Methods
340
269
 
341
- #### Token Verification
270
+ Authentication methods are configured in your Identity App settings. The SDK automatically shows only the methods you have enabled.
342
271
 
343
- ```javascript
344
- // Verify a token (validates expiration and issuer)
345
- const claims = await sv.identity.verifyToken(token);
346
- console.log(claims.identity); // Verified email or phone
347
- console.log(claims.identity_type); // 'email' or 'phone'
348
- console.log(claims.method); // Auth method used
349
- console.log(claims.verified_at); // Verification timestamp
350
- console.log(claims.exp); // Expiration timestamp
351
- ```
272
+ | Method | Description | Identity Type |
273
+ |--------|-------------|---------------|
274
+ | `passkey` | WebAuthn/FIDO2 biometric or security key | Email |
275
+ | `totp_email` | 6-digit code sent via email | Email |
276
+ | `totp_sms` | 6-digit code sent via SMS | Phone |
277
+ | `totp_voice` | 6-digit code sent via voice call | Phone |
278
+ | `magic_link` / `sparklink` | One-click magic link via email | Email |
279
+ | `google` | Sign in with Google | Email |
280
+ | `apple` | Sign in with Apple | Email |
281
+ | `microsoft` | Sign in with Microsoft | Email |
282
+ | `github` | Sign in with GitHub | Email |
283
+ | `facebook` | Sign in with Facebook | Email |
284
+ | `linkedin` | Sign in with LinkedIn | Email |
285
+
286
+ ## Backend Token Verification
352
287
 
353
- ### Sparks
288
+ > **Security Critical:** Always verify tokens on your backend. Never trust the frontend result alone. Tokens are signed with Ed25519 — use the JWKS endpoint to verify the signature.
354
289
 
355
- Create and read ephemeral encrypted secrets that burn on read.
290
+ ### Node.js
356
291
 
357
292
  ```javascript
358
- // Create a spark
359
- const spark = await sv.sparks.create({
360
- payload: 'secret data', // String or binary data
361
- ttl: 3600, // Time-to-live in seconds (default: 3600)
362
- maxReads: 1, // Max reads before burn (default: 1)
363
- password: 'optional-password' // Optional password protection
364
- });
293
+ import * as jose from 'jose';
365
294
 
366
- console.log(spark.id); // Spark ID
367
- console.log(spark.url); // Direct shareable URL
368
- console.log(spark.expiresAt); // Expiration timestamp
369
- console.log(spark.maxReads); // Remaining reads
295
+ const ACCOUNT_ID = 'acc_YOUR_ACCOUNT_ID';
296
+ const IDENTITY_URL = 'https://api.sparkvault.com/v1/apps/identity';
370
297
 
371
- // Read a spark (burns it)
372
- const data = await sv.sparks.read(sparkId);
373
- // or
374
- const data = await sv.sparks.read(sparkId, 'password');
298
+ app.post('/api/auth/login', async (req, res) => {
299
+ const { token } = req.body;
375
300
 
376
- console.log(data.payload); // Decrypted payload
377
- console.log(data.burned); // true if this was the last read
378
- ```
301
+ try {
302
+ // Fetch public keys and verify signature
303
+ const jwksUrl = `${IDENTITY_URL}/${ACCOUNT_ID}/.well-known/jwks.json`;
304
+ const JWKS = jose.createRemoteJWKSet(new URL(jwksUrl));
379
305
 
380
- ### Vaults
306
+ const { payload } = await jose.jwtVerify(token, JWKS, {
307
+ issuer: `${IDENTITY_URL}/${ACCOUNT_ID}`,
308
+ audience: ACCOUNT_ID
309
+ });
381
310
 
382
- Create and manage encrypted vaults with persistent storage for files (Ingots).
311
+ // Token is valid! Create user session
312
+ const user = await findOrCreateUser(payload.identity, payload.identity_type);
313
+ req.session.userId = user.id;
383
314
 
384
- ```javascript
385
- // Create a vault
386
- const vault = await sv.vaults.create({
387
- name: 'My Vault'
315
+ res.json({ success: true, user });
316
+ } catch (error) {
317
+ res.status(401).json({ error: 'Invalid token' });
318
+ }
388
319
  });
320
+ ```
389
321
 
390
- console.log(vault.id); // Vault ID
391
- console.log(vault.name); // Vault name
392
- console.log(vault.vmk); // Vault Master Key (24 chars) - SAVE THIS!
393
- console.log(vault.createdAt); // Creation timestamp
322
+ ### Python
394
323
 
395
- // List vaults
396
- const vaults = await sv.vaults.list();
324
+ ```python
325
+ import jwt
326
+ from jwt import PyJWKClient
397
327
 
398
- // Unseal a vault (required before accessing ingots)
399
- const unsealed = await sv.vaults.unseal(vaultId, vmk);
400
- console.log(unsealed.vatToken); // Vault Access Token
401
- console.log(unsealed.expiresAt); // Token expiration
328
+ ACCOUNT_ID = 'acc_YOUR_ACCOUNT_ID'
329
+ IDENTITY_URL = 'https://api.sparkvault.com/v1/apps/identity'
402
330
 
403
- // Upload a file (ingot)
404
- const ingot = await sv.vaults.uploadIngot(unsealed, {
405
- file: fileObject, // File or Blob
406
- name: 'document.pdf' // Display name
407
- });
331
+ def verify_identity_token(token: str):
332
+ """Verify a SparkVault Identity token and return the claims."""
333
+ jwks_url = f"{IDENTITY_URL}/{ACCOUNT_ID}/.well-known/jwks.json"
334
+ jwks_client = PyJWKClient(jwks_url)
408
335
 
409
- console.log(ingot.id); // Ingot ID
410
- console.log(ingot.name); // File name
411
- console.log(ingot.size); // File size in bytes
412
- console.log(ingot.mimeType); // MIME type
336
+ # Get the signing key
337
+ signing_key = jwks_client.get_signing_key_from_jwt(token)
413
338
 
414
- // List ingots
415
- const ingots = await sv.vaults.listIngots(unsealed);
339
+ # Verify and decode
340
+ claims = jwt.decode(
341
+ token,
342
+ signing_key.key,
343
+ algorithms=["EdDSA"],
344
+ issuer=f"{IDENTITY_URL}/{ACCOUNT_ID}",
345
+ audience=ACCOUNT_ID
346
+ )
416
347
 
417
- // Download an ingot
418
- const blob = await sv.vaults.downloadIngot(unsealed, ingotId);
419
-
420
- // Delete an ingot
421
- await sv.vaults.deleteIngot(unsealed, ingotId);
422
- ```
348
+ return claims
423
349
 
424
- ### RNG
350
+ # Usage in Flask
351
+ @app.route('/api/auth/login', methods=['POST'])
352
+ def login():
353
+ token = request.json.get('token')
425
354
 
426
- Generate cryptographically secure random numbers using hybrid entropy (KMS + local).
355
+ try:
356
+ claims = verify_identity_token(token)
427
357
 
428
- ```javascript
429
- // Generate random bytes
430
- const result = await sv.rng.generate({
431
- bytes: 32, // 1-1024 bytes
432
- format: 'hex' // Output format
433
- });
358
+ # Create session
359
+ user = find_or_create_user(claims['identity'], claims['identity_type'])
360
+ session['user_id'] = user.id
434
361
 
435
- console.log(result.value); // Random value in requested format
436
- console.log(result.bytes); // Number of bytes generated
437
- console.log(result.format); // Format used
438
-
439
- // Available formats
440
- // 'hex' - Hexadecimal string
441
- // 'base64' - Base64 encoded
442
- // 'base64url' - URL-safe Base64
443
- // 'bytes' - Raw Uint8Array
444
- // 'alphanumeric' - Alphanumeric string
445
- // 'numeric' - Numeric string
446
- // 'uuid' - UUID v4 format
447
- // 'password' - Password-safe characters
448
-
449
- // Convenience methods
450
- const uuid = await sv.rng.uuid(); // UUID v4
451
- const password = await sv.rng.password(16); // 16-char password
362
+ return jsonify({'success': True, 'user': user.to_dict()})
363
+ except jwt.InvalidTokenError as e:
364
+ return jsonify({'error': 'Invalid token'}), 401
452
365
  ```
453
366
 
454
- ## Configuration
367
+ ### Go
368
+
369
+ ```go
370
+ package main
371
+
372
+ import (
373
+ "github.com/lestrrat-go/jwx/v2/jwk"
374
+ "github.com/lestrrat-go/jwx/v2/jwt"
375
+ )
376
+
377
+ const (
378
+ AccountID = "acc_YOUR_ACCOUNT_ID"
379
+ IdentityURL = "https://api.sparkvault.com/v1/apps/identity"
380
+ )
381
+
382
+ func verifyToken(tokenString string) (jwt.Token, error) {
383
+ jwksURL := fmt.Sprintf("%s/%s/.well-known/jwks.json", IdentityURL, AccountID)
384
+
385
+ // Fetch JWKS
386
+ keySet, err := jwk.Fetch(context.Background(), jwksURL)
387
+ if err != nil {
388
+ return nil, err
389
+ }
390
+
391
+ // Parse and verify token
392
+ token, err := jwt.Parse(
393
+ []byte(tokenString),
394
+ jwt.WithKeySet(keySet),
395
+ jwt.WithIssuer(fmt.Sprintf("%s/%s", IdentityURL, AccountID)),
396
+ jwt.WithAudience(AccountID),
397
+ )
398
+ if err != nil {
399
+ return nil, err
400
+ }
401
+
402
+ return token, nil
403
+ }
455
404
 
456
- ```javascript
457
- const sv = SparkVault.init({
458
- // Required
459
- accountId: 'acc_your_account', // Your SparkVault account ID
460
-
461
- // Optional
462
- locale: 'en', // Default UI locale
463
- timeout: 30000, // Request timeout in ms (default: 30000)
464
- preloadConfig: true // Preload Identity config on init (default: true)
465
- });
405
+ // Usage in HTTP handler
406
+ func loginHandler(w http.ResponseWriter, r *http.Request) {
407
+ var body struct {
408
+ Token string `json:"token"`
409
+ }
410
+ json.NewDecoder(r.Body).Decode(&body)
411
+
412
+ token, err := verifyToken(body.Token)
413
+ if err != nil {
414
+ http.Error(w, "Invalid token", http.StatusUnauthorized)
415
+ return
416
+ }
417
+
418
+ identity, _ := token.Get("identity")
419
+ // Create session with identity...
420
+ }
466
421
  ```
467
422
 
468
- ### Configuration Options
469
-
470
- | Option | Type | Default | Description |
471
- |--------|------|---------|-------------|
472
- | `accountId` | `string` | **required** | Your SparkVault account ID (starts with `acc_`) |
473
- | `locale` | `string` | `'en'` | Default UI locale for the Identity modal |
474
- | `timeout` | `number` | `30000` | Request timeout in milliseconds |
475
- | `preloadConfig` | `boolean` | `true` | Preload Identity configuration on SDK init |
476
-
477
- ### Config Preloading
423
+ ### Ruby
424
+
425
+ ```ruby
426
+ require 'jwt'
427
+ require 'net/http'
428
+ require 'json'
429
+
430
+ ACCOUNT_ID = 'acc_YOUR_ACCOUNT_ID'
431
+ IDENTITY_URL = 'https://api.sparkvault.com/v1/apps/identity'
432
+
433
+ def verify_identity_token(token)
434
+ jwks_url = "#{IDENTITY_URL}/#{ACCOUNT_ID}/.well-known/jwks.json"
435
+
436
+ # Fetch JWKS
437
+ uri = URI(jwks_url)
438
+ response = Net::HTTP.get(uri)
439
+ jwks = JSON.parse(response)
440
+
441
+ # Decode and verify
442
+ JWT.decode(
443
+ token,
444
+ nil,
445
+ true,
446
+ {
447
+ algorithms: ['EdDSA'],
448
+ iss: "#{IDENTITY_URL}/#{ACCOUNT_ID}",
449
+ aud: ACCOUNT_ID,
450
+ verify_iss: true,
451
+ verify_aud: true,
452
+ jwks: jwks
453
+ }
454
+ ).first
455
+ end
456
+
457
+ # Usage in Rails controller
458
+ class AuthController < ApplicationController
459
+ def login
460
+ claims = verify_identity_token(params[:token])
461
+
462
+ user = User.find_or_create_by(email: claims['identity'])
463
+ session[:user_id] = user.id
464
+
465
+ render json: { success: true, user: user }
466
+ rescue JWT::DecodeError => e
467
+ render json: { error: 'Invalid token' }, status: :unauthorized
468
+ end
469
+ end
470
+ ```
478
471
 
479
- By default, the SDK preloads your Identity configuration in the background when `SparkVault.init()` is called. This means when the user clicks your "Sign In" button and you call `pop()`, the modal opens **instantly** without a loading spinner.
472
+ ### Token Claims
480
473
 
481
- ```javascript
482
- // Default behavior (preloadConfig: true)
483
- // Config is fetched immediately when SDK initializes
484
- const sv = SparkVault.init({ accountId: 'acc_xxx' });
474
+ When decoded, the JWT token contains these claims:
485
475
 
486
- // Later, when user clicks "Sign In"...
487
- await sv.identity.pop(); // Opens instantly - config already loaded!
476
+ ```json
477
+ {
478
+ "iss": "https://api.sparkvault.com/v1/apps/identity/acc_xxx",
479
+ "sub": "identity:a1b2c3d4e5f67890",
480
+ "aud": "acc_xxx",
481
+ "identity": "user@example.com",
482
+ "identity_type": "email",
483
+ "method": "totp_email",
484
+ "verified_at": 1703977195,
485
+ "iat": 1703977200,
486
+ "exp": 1703980800,
487
+ "jti": "tok_unique_id"
488
+ }
488
489
  ```
489
490
 
490
- If you want to defer config loading until `pop()` is called (the legacy behavior), disable preloading:
491
-
492
- ```javascript
493
- // Disable preloading - config loads when pop() is called
494
- const sv = SparkVault.init({
495
- accountId: 'acc_xxx',
496
- preloadConfig: false
491
+ | Claim | Type | Description |
492
+ |-------|------|-------------|
493
+ | `iss` | `string` | Issuer URL — always verify this matches expected value |
494
+ | `sub` | `string` | Subject hashed identity for consistent user identification |
495
+ | `aud` | `string` | Audience — your account ID |
496
+ | `identity` | `string` | The verified email address or phone number |
497
+ | `identity_type` | `string` | `"email"` or `"phone"` |
498
+ | `method` | `string` | Authentication method used (e.g., `"totp_email"`, `"passkey"`) |
499
+ | `verified_at` | `number` | Unix timestamp when identity was verified |
500
+ | `iat` | `number` | Issued at timestamp |
501
+ | `exp` | `number` | Expiration timestamp |
502
+ | `jti` | `string` | Unique token ID for replay protection |
503
+
504
+ ## React Integration
505
+
506
+ ```jsx
507
+ import { useState, useCallback } from 'react';
508
+ import SparkVault from '@sparkvault/sdk';
509
+
510
+ // Initialize once, outside component
511
+ const sparkvault = SparkVault.init({
512
+ accountId: 'acc_YOUR_ACCOUNT_ID'
497
513
  });
498
514
 
499
- // Config loads on-demand with a brief loading spinner
500
- await sv.identity.pop();
501
- ```
515
+ function useSparkVaultAuth() {
516
+ const [user, setUser] = useState(null);
517
+ const [loading, setLoading] = useState(false);
518
+ const [error, setError] = useState(null);
519
+
520
+ const login = useCallback(async (options = {}) => {
521
+ setLoading(true);
522
+ setError(null);
523
+
524
+ try {
525
+ // Open verification modal
526
+ const result = await sparkvault.identity.pop(options);
527
+
528
+ // Send token to backend
529
+ const response = await fetch('/api/auth/login', {
530
+ method: 'POST',
531
+ headers: { 'Content-Type': 'application/json' },
532
+ body: JSON.stringify({ token: result.token })
533
+ });
534
+
535
+ if (!response.ok) throw new Error('Login failed');
536
+
537
+ const data = await response.json();
538
+ setUser(data.user);
539
+ return data.user;
540
+
541
+ } catch (err) {
542
+ if (err.code !== 'user_cancelled') {
543
+ setError(err.message);
544
+ }
545
+ throw err;
546
+ } finally {
547
+ setLoading(false);
548
+ }
549
+ }, []);
550
+
551
+ const logout = useCallback(async () => {
552
+ await fetch('/api/auth/logout', { method: 'POST' });
553
+ setUser(null);
554
+ }, []);
555
+
556
+ return { user, loading, error, login, logout };
557
+ }
502
558
 
503
- ### Environments
559
+ // Usage in component
560
+ function LoginPage() {
561
+ const { user, loading, error, login, logout } = useSparkVaultAuth();
562
+
563
+ if (user) {
564
+ return (
565
+ <div>
566
+ <p>Welcome, {user.email}!</p>
567
+ <button onClick={logout}>Sign out</button>
568
+ </div>
569
+ );
570
+ }
504
571
 
505
- | Environment | API Base URL | Use Case |
506
- |-------------|--------------|----------|
507
- | `production` | `https://api.sparkvault.com` | Live applications |
508
- | `sandbox` | `https://sandbox.api.sparkvault.com` | Testing and development |
572
+ return (
573
+ <div>
574
+ <button onClick={() => login()} disabled={loading}>
575
+ {loading ? 'Verifying...' : 'Sign in'}
576
+ </button>
577
+ {error && <p className="error">{error}</p>}
578
+ </div>
579
+ );
580
+ }
581
+ ```
509
582
 
510
583
  ## Error Handling
511
584
 
512
- The SDK provides typed errors for different failure scenarios:
585
+ The SDK throws typed errors that you can catch and handle appropriately.
513
586
 
514
587
  ```javascript
515
- import {
516
- SparkVaultError, // Base error class
517
- ValidationError, // Invalid input (400)
518
- AuthenticationError, // Not authenticated (401)
519
- AuthorizationError, // Not authorized (403)
520
- NetworkError, // Network failure
521
- TimeoutError, // Request timeout
522
- PopupBlockedError, // Popup was blocked
523
- UserCancelledError // User closed popup
588
+ import SparkVault, {
589
+ UserCancelledError,
590
+ ValidationError,
591
+ NetworkError,
592
+ TimeoutError
524
593
  } from '@sparkvault/sdk';
525
594
 
526
595
  try {
527
- await sv.identity.pop();
596
+ const result = await sparkvault.identity.pop();
528
597
  } catch (error) {
529
- if (error instanceof PopupBlockedError) {
530
- alert('Please allow popups for this site');
531
- } else if (error instanceof UserCancelledError) {
532
- console.log('User cancelled verification');
533
- } else if (error instanceof ValidationError) {
534
- console.error('Validation failed:', error.message);
535
- console.error('Details:', error.details);
536
- } else if (error instanceof AuthenticationError) {
537
- console.error('Authentication failed:', error.message);
538
- } else if (error instanceof NetworkError) {
539
- console.error('Network error:', error.message);
540
- } else if (error instanceof TimeoutError) {
541
- console.error('Request timed out');
542
- } else {
543
- console.error('Unknown error:', error);
598
+ switch (error.code) {
599
+ case 'user_cancelled':
600
+ // User closed the modal not necessarily an error
601
+ console.log('User cancelled verification');
602
+ break;
603
+
604
+ case 'validation_error':
605
+ // Invalid input (e.g., malformed email)
606
+ console.error('Validation error:', error.message);
607
+ break;
608
+
609
+ case 'network_error':
610
+ // Network connectivity issue
611
+ console.error('Network error — please check your connection');
612
+ break;
613
+
614
+ case 'timeout_error':
615
+ // Request timed out
616
+ console.error('Request timed out — please try again');
617
+ break;
618
+
619
+ case 'popup_blocked':
620
+ // Browser blocked the popup
621
+ console.error('Please allow popups for this site');
622
+ break;
623
+
624
+ default:
625
+ console.error('Unexpected error:', error.message);
544
626
  }
545
627
  }
546
628
  ```
547
629
 
548
- ### Error Properties
630
+ ### Error Types
549
631
 
550
- All errors extend `SparkVaultError` and include:
632
+ | Error Class | Code | Description |
633
+ |-------------|------|-------------|
634
+ | `UserCancelledError` | `user_cancelled` | User closed the modal without completing verification |
635
+ | `ValidationError` | `validation_error` | Invalid input or configuration |
636
+ | `NetworkError` | `network_error` | Network connectivity failure |
637
+ | `TimeoutError` | `timeout_error` | Request exceeded timeout limit |
638
+ | `PopupBlockedError` | `popup_blocked` | Browser blocked the popup window |
639
+ | `AuthenticationError` | `authentication_error` | Authentication failed (e.g., expired token) |
551
640
 
552
- ```typescript
553
- interface SparkVaultError extends Error {
554
- message: string; // Human-readable message
555
- code: string; // Error code (e.g., 'validation_error')
556
- statusCode?: number; // HTTP status code if applicable
557
- details?: Record<string, any>; // Additional error details
558
- }
559
- ```
560
-
561
- ## TypeScript
641
+ ## TypeScript Support
562
642
 
563
- The SDK is written in TypeScript and includes full type definitions:
643
+ The SDK is written in TypeScript and includes full type definitions.
564
644
 
565
645
  ```typescript
566
- import {
567
- SparkVault,
646
+ import SparkVault, {
647
+ // Configuration
568
648
  SparkVaultConfig,
649
+
650
+ // Identity types
569
651
  VerifyOptions,
570
- RenderOptions,
571
652
  VerifyResult,
572
653
  TokenClaims,
573
- AuthMethod,
574
- CreateSparkOptions,
575
- Spark,
576
- SparkPayload,
577
- CreateVaultOptions,
578
- Vault,
579
- UnsealedVault,
580
- Ingot,
581
- UploadIngotOptions,
582
- GenerateOptions,
583
- RandomResult,
584
- RNGFormat
585
- } from '@sparkvault/sdk';
586
-
587
- // All methods are fully typed
588
- const sv = SparkVault.init({
589
- accountId: 'acc_test'
590
- });
591
654
 
592
- // TypeScript knows the return types
593
- const result: VerifyResult = await sv.identity.pop();
594
- ```
655
+ // Error types
656
+ SparkVaultError,
657
+ UserCancelledError,
658
+ ValidationError,
659
+ NetworkError,
660
+ TimeoutError,
661
+ PopupBlockedError,
662
+ } from '@sparkvault/sdk';
595
663
 
596
- ## Security
664
+ // Fully typed configuration
665
+ const config: SparkVaultConfig = {
666
+ accountId: 'acc_YOUR_ACCOUNT_ID',
667
+ timeout: 30000,
668
+ };
597
669
 
598
- ### Token Security
670
+ const sparkvault = SparkVault.init(config);
599
671
 
600
- - All tokens are Ed25519-signed JWTs
601
- - Tokens are validated for expiration and issuer
602
- - Tokens are short-lived (1 hour default) and scoped to your account
603
- - **Tokens are single-use verification proofs** — not session tokens
672
+ // Fully typed options and result
673
+ const options: VerifyOptions = {
674
+ email: 'user@example.com',
675
+ onCancel: () => console.log('Cancelled'),
676
+ };
604
677
 
605
- ### Session Management
678
+ const result: VerifyResult = await sparkvault.identity.pop(options);
679
+ // result.token: string
680
+ // result.identity: string
681
+ // result.identityType: 'email' | 'phone'
682
+ ```
606
683
 
607
- SparkVault Identity follows the same pattern as "Login with Google" and other major IdPs:
684
+ ## Best Practices
608
685
 
609
- 1. **SparkVault verifies identity** Returns signed ID token
610
- 2. **Your backend validates the token** → Checks signature via JWKS
611
- 3. **Your backend creates YOUR session** → Cookie, JWT, database session
612
- 4. **Token is discarded** → Your session takes over
686
+ 1. **Initialize Once** — Create the SparkVault instance once when your app loads, not on every login attempt. The SDK preloads configuration for instant modal opening.
613
687
 
614
- There are no refresh tokens by design. Your application owns the user relationship and decides session lifetime, storage, and re-verification policy. This is the industry standard approach used by Google, Okta, Auth0, and all major identity providers.
688
+ 2. **Always Verify Server-Side** Never trust client-side token validation in production. Always verify the JWT signature on your backend using the JWKS endpoint before creating a session.
615
689
 
616
- ### Popup Security
690
+ 3. **Handle Cancellation Gracefully** — Users may close the modal without completing verification. This throws a `UserCancelledError` — handle it gracefully rather than showing an error message.
617
691
 
618
- - Origin validation prevents cross-origin attacks
619
- - `postMessage` is used securely with origin checking
620
- - Popups are isolated from your main window context
692
+ 4. **Cache JWKS Keys** — The JWKS endpoint returns public keys with caching headers. Use a library like `jose` that handles caching automatically, or cache keys for 5-10 minutes.
621
693
 
622
- ### API Security
694
+ ## Browser Support
623
695
 
624
- - All requests use HTTPS
625
- - The SDK only requires your account ID (no secrets in client-side code)
696
+ The SDK supports all modern browsers:
626
697
 
627
- ### Best Practices
698
+ - Chrome 80+
699
+ - Firefox 75+
700
+ - Safari 13.1+
701
+ - Edge 80+
628
702
 
629
- 1. **Validate tokens server-side** - Don't trust client-side token validation alone
630
- 2. **Use HTTPS** - Always serve your application over HTTPS
631
- 3. **Keep VMKs secure** - Vault Master Keys should be stored securely by users
703
+ **Note:** Passkey authentication requires WebAuthn support. On unsupported browsers, passkey will not appear as an option — other methods will still work.
632
704
 
633
705
  ## Development
634
706
 
@@ -660,19 +732,7 @@ sdk-js/
660
732
  │ ├── config.ts # Configuration management
661
733
  │ ├── http.ts # HTTP client
662
734
  │ ├── errors.ts # Error types
663
- ├── identity/ # Identity module
664
- │ │ ├── index.ts
665
- │ │ ├── modal.ts # Popup manager
666
- │ │ └── types.ts
667
- │ ├── sparks/ # Sparks module
668
- │ │ ├── index.ts
669
- │ │ └── types.ts
670
- │ ├── vaults/ # Vaults module
671
- │ │ ├── index.ts
672
- │ │ └── types.ts
673
- │ └── rng/ # RNG module
674
- │ ├── index.ts
675
- │ └── types.ts
735
+ └── identity/ # Identity module
676
736
  ├── dist/ # Built bundles
677
737
  ├── tests/ # Test files
678
738
  ├── .github/workflows/ # CI/CD workflows
@@ -681,32 +741,6 @@ sdk-js/
681
741
  └── package.json
682
742
  ```
683
743
 
684
- ### CI/CD
685
-
686
- The SDK uses GitHub Actions for automated testing and deployment:
687
-
688
- - **On PR**: Runs build, lint, typecheck, and tests
689
- - **On push to main**: Deploys to CDN (`cdn.sparkvault.com`)
690
- - **On release commit**: Publishes to npm
691
-
692
- Required secrets for deployment:
693
- - `CLOUDFLARE_API_TOKEN` - Cloudflare API token with R2 access
694
- - `CLOUDFLARE_ACCOUNT_ID` - Cloudflare account ID
695
- - `NPM_TOKEN` - npm publish token (for releases)
696
-
697
- ## Browser Support
698
-
699
- - Chrome 80+
700
- - Firefox 75+
701
- - Safari 13.1+
702
- - Edge 80+
703
-
704
- The SDK requires:
705
- - `fetch` API
706
- - `Promise`
707
- - `window.open` (for Identity popup)
708
- - `postMessage` (for popup communication)
709
-
710
744
  ## License
711
745
 
712
746
  MIT License - see [LICENSE](LICENSE) for details.