@sparkvault/sdk-js
v4.1.0
Published
SparkVault JavaScript SDK - Identity Verification and Encrypted Vaults
Maintainers
Readme
SparkVault JavaScript SDK
The official SparkVault JavaScript SDK for browser applications. Add passwordless verification to your app in minutes with CDN auto-init or full programmatic control.
Table of Contents
- Installation
- CDN Auto-Init
- Manual Initialization
- Identity Verification
- Backend Token Verification
- Full OIDC/BFF Session Ownership
- React Integration
- Error Handling
- TypeScript Support
- Browser Support
- Build & Release
- License
Installation
CDN (Recommended)
<script src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"></script>npm / yarn
npm install @sparkvault/sdk-js
# or
yarn add @sparkvault/sdk-jsBundle Formats:
sparkvault.js— UMD bundle for browsers (recommended)sparkvault.esm.js— ES modules for modern bundlerssparkvault.cjs.js— CommonJS for Node.js
CDN Auto-Init
<script
async
src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"
data-account-id="acc_YOUR_ACCOUNT_ID"
></script>
<button class="login-btn">Sign In</button>
<script>
SparkVault.products.identity.attach('.login-btn', {
onSuccess: async (result) => {
await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: result.token })
});
}
});
</script>The script auto-initializes SparkVault with your account ID. Use
SparkVault.products.identity.attach() or SparkVault.products.identity.pop() to open the
verification UI and handle the result.
Data Attributes
| Attribute | Required | Description |
|-----------|----------|-------------|
| data-account-id | Yes | Your SparkVault account ID (required for auto-init) |
| data-debug | No | Set to "true" for verbose console logging |
Complete Example
<script
async
src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"
data-account-id="acc_YOUR_ACCOUNT_ID"
data-debug="true"
></script>
<script>
SparkVault.products.identity.attach('.js-sparkvault-auth', {
onSuccess: async (result) => {
const response = await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: result.token })
});
if (!response.ok) throw new Error('Verification failed');
window.location.assign('/dashboard');
},
onError: (error) => {
console.error('SparkVault verification failed:', error.message);
}
});
</script>
<!-- Multiple elements can use the same selector -->
<button class="js-sparkvault-auth">Sign In</button>
<a href="#" class="js-sparkvault-auth">Login with SparkVault</a>Verification Result
The SDK returns this shape from products.identity.pop(), products.identity.verify(), and
products.identity.attach() callbacks:
{
"token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
"identity": "[email protected]",
"identityType": "email",
"svid": "ing_019e66a4e27875f2822ede0e4f5d8792",
"sessionId": "sess_abc123"
}Send token to your backend and verify it with the Identity JWKS before
trusting any claim. Use svid as the canonical user key in your application.
Manual Trigger from JavaScript
You can also trigger authentication programmatically using the SparkVault global:
// Trigger authentication from anywhere in your code
const result = await SparkVault.identity.pop();
await sendTokenToBackend(result.token);<button onclick="SparkVault.identity.pop()">
Sign In with SparkVault
</button>Manual Initialization
For full control over the SDK, initialize it manually in JavaScript:
import SparkVault from '@sparkvault/sdk-js';
const sparkvault = SparkVault.init({
accountId: 'acc_YOUR_ACCOUNT_ID' // Required
});
// The SDK is now ready to use
const result = await sparkvault.identity.pop();Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| accountId | string | required | Your SparkVault account ID (starts with acc_) |
| timeout | number | 30000 | HTTP request timeout in milliseconds |
| preloadConfig | boolean | true | Preload Identity config for instant modal |
CDN Usage
<script src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"></script>
<script>
const sparkvault = SparkVault.init({
accountId: 'acc_YOUR_ACCOUNT_ID'
});
document.getElementById('login-btn').addEventListener('click', async () => {
const result = await sparkvault.identity.pop();
console.log('Verified:', result.identity);
});
</script>Identity Verification
The Identity module provides passwordless authentication through a beautiful, customizable modal. Users can verify via passkeys, email codes, SMS codes, SparkLink, or social providers.
Basic Usage
// Open the verification modal
const result = await sparkvault.identity.pop();
// User verified!
console.log(result.identity); // "[email protected]" or "+14155551234"
console.log(result.identityType); // "email" or "phone"
console.log(result.token); // Signed JWT token
// Send the token to your backend for verification
await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: result.token })
});pop() Options
| Option | Type | Description |
|--------|------|-------------|
| email | string | Pre-fill email address (mutually exclusive with phone) |
| phone | string | Pre-fill phone in E.164 format, e.g. "+14155551234" |
| backdropBlur | boolean | Override backdrop blur setting from app config |
| onCancel | function | Callback when user closes modal without verifying |
| onSuccess | function | Callback on success (before promise resolves) |
| onError | function | Callback on error (before promise rejects) |
pop() Result
| Field | Type | Description |
|-------|------|-------------|
| token | string | Signed JWT token (Ed25519). Verify this on your backend. |
| identity | string | The verified email address or phone number |
| identityType | "email" \| "phone" | Type of identity that was verified |
| svid | string | Canonical SparkVault ID for the person |
| sessionId | string | Managed Identity session ID |
| refreshToken | string | Present only for flows that intentionally return managed-session refresh material |
Example with Options
try {
const result = await sparkvault.identity.pop({
// Pre-fill the email field
email: '[email protected]',
// Handle cancellation
onCancel: () => {
console.log('User cancelled');
}
});
// Success! Send token to your backend
await loginWithToken(result.token);
} catch (error) {
if (error.code === 'user_cancelled') {
// User closed the modal
} else {
// Handle other errors
console.error('Verification failed:', error.message);
}
}Authentication Methods
Authentication methods are configured in your Identity Product settings. The SDK automatically shows only the methods you have enabled.
| Method | Description | Identity Type |
|--------|-------------|---------------|
| passkey | WebAuthn/FIDO2 biometric or security key | Email |
| totp_email | 6-digit code sent via email | Email |
| totp_sms | 6-digit code sent via SMS | Phone |
| totp_voice | 6-digit code sent via voice call | Phone |
| sparklink | One-click sign-in link via email | Email |
| social_google | Sign in with Google | Social account |
| social_apple | Sign in with Apple | Social account |
| social_microsoft | Sign in with Microsoft | Social account |
| social_github | Sign in with GitHub | Social account |
| social_facebook | Sign in with Facebook | Social account |
| social_linkedin | Sign in with LinkedIn | Social account |
Backend Token Verification
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.
Node.js
import * as jose from 'jose';
const ACCOUNT_ID = 'acc_YOUR_ACCOUNT_ID';
const IDENTITY_URL = 'https://api.sparkvault.com/v1/products/identity';
app.post('/api/auth/login', async (req, res) => {
const { token } = req.body;
try {
// Fetch public keys and verify signature
const jwksUrl = `${IDENTITY_URL}/${ACCOUNT_ID}/.well-known/jwks.json`;
const JWKS = jose.createRemoteJWKSet(new URL(jwksUrl));
const { payload } = await jose.jwtVerify(token, JWKS, {
issuer: `${IDENTITY_URL}/${ACCOUNT_ID}`,
audience: ACCOUNT_ID,
algorithms: ['EdDSA']
});
// Token is valid. SVID is the canonical identity reference.
const svid = payload.svid || payload.sub;
const user = await findOrCreateUserBySvid(svid, {
identity: payload.identity,
identityType: payload.identity_type
});
req.session.userId = user.id;
res.json({ success: true, user });
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
});Python
import jwt
from jwt import PyJWKClient
ACCOUNT_ID = 'acc_YOUR_ACCOUNT_ID'
IDENTITY_URL = 'https://api.sparkvault.com/v1/products/identity'
def verify_identity_token(token: str):
"""Verify a SparkVault Identity token and return the claims."""
jwks_url = f"{IDENTITY_URL}/{ACCOUNT_ID}/.well-known/jwks.json"
jwks_client = PyJWKClient(jwks_url)
# Get the signing key
signing_key = jwks_client.get_signing_key_from_jwt(token)
# Verify and decode
claims = jwt.decode(
token,
signing_key.key,
algorithms=["EdDSA"],
issuer=f"{IDENTITY_URL}/{ACCOUNT_ID}",
audience=ACCOUNT_ID
)
return claims
# Usage in Flask
@app.route('/api/auth/login', methods=['POST'])
def login():
token = request.json.get('token')
try:
claims = verify_identity_token(token)
# SVID is the canonical identity reference.
svid = claims.get('svid') or claims['sub']
user = find_or_create_user_by_svid(
svid,
identity=claims['identity'],
identity_type=claims['identity_type']
)
session['user_id'] = user.id
return jsonify({'success': True, 'user': user.to_dict()})
except jwt.InvalidTokenError as e:
return jsonify({'error': 'Invalid token'}), 401Go
package main
import (
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/jwx/v2/jwt"
)
const (
AccountID = "acc_YOUR_ACCOUNT_ID"
IdentityURL = "https://api.sparkvault.com/v1/products/identity"
)
func verifyToken(tokenString string) (jwt.Token, error) {
jwksURL := fmt.Sprintf("%s/%s/.well-known/jwks.json", IdentityURL, AccountID)
// Fetch JWKS
keySet, err := jwk.Fetch(context.Background(), jwksURL)
if err != nil {
return nil, err
}
// Parse and verify token
token, err := jwt.Parse(
[]byte(tokenString),
jwt.WithKeySet(keySet),
jwt.WithIssuer(fmt.Sprintf("%s/%s", IdentityURL, AccountID)),
jwt.WithAudience(AccountID),
)
if err != nil {
return nil, err
}
return token, nil
}
// Usage in HTTP handler
func loginHandler(w http.ResponseWriter, r *http.Request) {
var body struct {
Token string `json:"token"`
}
json.NewDecoder(r.Body).Decode(&body)
token, err := verifyToken(body.Token)
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
svid, _ := token.Get("svid")
if svid == nil {
svid, _ = token.Subject()
}
// Find or create your local user by SVID.
}Ruby
require 'jwt'
require 'net/http'
require 'json'
ACCOUNT_ID = 'acc_YOUR_ACCOUNT_ID'
IDENTITY_URL = 'https://api.sparkvault.com/v1/products/identity'
def verify_identity_token(token)
jwks_url = "#{IDENTITY_URL}/#{ACCOUNT_ID}/.well-known/jwks.json"
# Fetch JWKS
uri = URI(jwks_url)
response = Net::HTTP.get(uri)
jwks = JSON.parse(response)
# Decode and verify
JWT.decode(
token,
nil,
true,
{
algorithms: ['EdDSA'],
iss: "#{IDENTITY_URL}/#{ACCOUNT_ID}",
aud: ACCOUNT_ID,
verify_iss: true,
verify_aud: true,
jwks: jwks
}
).first
end
# Usage in Rails controller
class AuthController < ApplicationController
def login
claims = verify_identity_token(params[:token])
svid = claims['svid'] || claims['sub']
user = User.find_or_create_by!(svid: svid)
session[:user_id] = user.id
render json: { success: true, user: user }
rescue JWT::DecodeError => e
render json: { error: 'Invalid token' }, status: :unauthorized
end
endToken Claims
When decoded, the JWT token contains these claims:
{
"iss": "https://api.sparkvault.com/v1/products/identity/acc_xxx",
"sub": "ing_019e66a4e27875f2822ede0e4f5d8792",
"aud": "acc_xxx",
"svid": "ing_019e66a4e27875f2822ede0e4f5d8792",
"session_id": "sess_abc123",
"token_version": 0,
"identity": "[email protected]",
"identity_type": "email",
"method": "totp_email",
"verified_at": 1703977195,
"iat": 1703977200,
"exp": 1703980800,
"jti": "tok_unique_id"
}| Claim | Type | Description |
|-------|------|-------------|
| iss | string | Issuer URL — always verify this matches expected value |
| sub | string | Subject — equals the SVID for managed Identity tokens |
| aud | string | Audience — your account ID |
| svid | string | Canonical SparkVault ID for the person |
| session_id | string | Managed Identity session ID |
| token_version | number | Managed-session version used for revocation/introspection |
| identity | string | The verified email address or phone number |
| identity_type | string | "email" or "phone" |
| method | string | Authentication method used (e.g., "totp_email", "passkey") |
| verified_at | number | Unix timestamp when identity was verified |
| iat | number | Issued at timestamp |
| exp | number | Expiration timestamp |
| jti | string | Unique token ID for replay protection |
Full OIDC/BFF Session Ownership
The JavaScript SDK simple flow is a signed verification proof. It is ideal when your backend already owns app sessions and only needs to know which SVID was verified.
For replacing a production login/session system, use SparkVault Identity as the OIDC provider and put a backend-for-frontend (BFF) boundary in front of your application:
- Redirect the browser to
/v1/products/identity/{account_id}/authorizewith PKCE. - Exchange the callback
codeforid_token,access_token, andrefresh_tokenserver-side. - Store refresh/session material only in encrypted or signed
HttpOnly,Secure,SameSite=Laxcookies. - Keep refresh tokens out of browser JavaScript.
- Validate access JWTs locally with cached JWKS for ordinary API requests.
- Call
/introspectonly for sensitive mutations, account settings, admin/moderation actions, suspicious sessions, or short cached hard checks. - On logout, call
/revokewith the current refresh token, then clear local cookies.
Application data should key people by svid. Keep username, roles, profile,
preferences, uploads, and moderation state in your application database.
React Integration
import { useState, useCallback } from 'react';
import SparkVault from '@sparkvault/sdk-js';
// Initialize once, outside component
const sparkvault = SparkVault.init({
accountId: 'acc_YOUR_ACCOUNT_ID'
});
function useSparkVaultAuth() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const login = useCallback(async (options = {}) => {
setLoading(true);
setError(null);
try {
// Open verification modal
const result = await sparkvault.identity.pop(options);
// Send token to backend
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: result.token })
});
if (!response.ok) throw new Error('Login failed');
const data = await response.json();
setUser(data.user);
return data.user;
} catch (err) {
if (err.code !== 'user_cancelled') {
setError(err.message);
}
throw err;
} finally {
setLoading(false);
}
}, []);
const logout = useCallback(async () => {
await fetch('/api/auth/logout', { method: 'POST' });
setUser(null);
}, []);
return { user, loading, error, login, logout };
}
// Usage in component
function LoginPage() {
const { user, loading, error, login, logout } = useSparkVaultAuth();
if (user) {
return (
<div>
<p>Welcome, {user.email}!</p>
<button onClick={logout}>Sign out</button>
</div>
);
}
return (
<div>
<button onClick={() => login()} disabled={loading}>
{loading ? 'Verifying...' : 'Sign in'}
</button>
{error && <p className="error">{error}</p>}
</div>
);
}Error Handling
The SDK throws typed errors that you can catch and handle appropriately.
import SparkVault, {
UserCancelledError,
ValidationError,
NetworkError,
TimeoutError
} from '@sparkvault/sdk-js';
try {
const result = await sparkvault.identity.pop();
} catch (error) {
switch (error.code) {
case 'user_cancelled':
// User closed the modal — not necessarily an error
console.log('User cancelled verification');
break;
case 'validation_error':
// Invalid input (e.g., malformed email)
console.error('Validation error:', error.message);
break;
case 'network_error':
// Network connectivity issue
console.error('Network error — please check your connection');
break;
case 'timeout_error':
// Request timed out
console.error('Request timed out — please try again');
break;
case 'popup_blocked':
// Browser blocked the popup
console.error('Please allow popups for this site');
break;
default:
console.error('Unexpected error:', error.message);
}
}Error Types
| Error Class | Code | Description |
|-------------|------|-------------|
| UserCancelledError | user_cancelled | User closed the modal without completing verification |
| ValidationError | validation_error | Invalid input or configuration |
| NetworkError | network_error | Network connectivity failure |
| TimeoutError | timeout_error | Request exceeded timeout limit |
| PopupBlockedError | popup_blocked | Browser blocked the popup window |
| AuthenticationError | authentication_error | Authentication failed (e.g., expired token) |
TypeScript Support
The SDK is written in TypeScript and includes full type definitions.
import SparkVault, {
// Configuration
SparkVaultConfig,
// Identity types
VerifyOptions,
VerifyResult,
TokenClaims,
// Error types
SparkVaultError,
UserCancelledError,
ValidationError,
NetworkError,
TimeoutError,
PopupBlockedError,
} from '@sparkvault/sdk-js';
// Fully typed configuration
const config: SparkVaultConfig = {
accountId: 'acc_YOUR_ACCOUNT_ID',
timeout: 30000,
};
const sparkvault = SparkVault.init(config);
// Fully typed options and result
const options: VerifyOptions = {
email: '[email protected]',
onCancel: () => console.log('Cancelled'),
};
const result: VerifyResult = await sparkvault.identity.pop(options);
// result.token: string
// result.identity: string
// result.identityType: 'email' | 'phone'Content Security Policy (CSP)
If your site uses a Content Security Policy, you must allow the SparkVault domains:
connect-src https://api.sparkvault.com wss://ws.sparkvault.com;
script-src https://cdn.sparkvault.com;| Directive | Domain | Reason |
|-----------|--------|--------|
| connect-src | https://api.sparkvault.com | API requests (identity verification, config) |
| connect-src | wss://ws.sparkvault.com | WebSocket for SparkLink push notifications |
| script-src | https://cdn.sparkvault.com | SDK script (if using CDN installation) |
Best Practices
Initialize Once — Create the SparkVault instance once when your app loads, not on every login attempt. The SDK preloads configuration for instant modal opening.
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 trusting claims or issuing an app session.
Handle Cancellation Gracefully — Users may close the modal without completing verification. This throws a
UserCancelledError— handle it gracefully rather than showing an error message.Key Users by SVID — Use
svidorsubas the canonical identity reference. Email and phone are user-facing attributes, not durable account keys.Cache JWKS Keys — The JWKS endpoint returns public keys with caching headers. Use a library like
josethat handles caching automatically, or cache keys for 5-10 minutes.Use OIDC/BFF for Managed Sessions — The SDK simple flow gives you a signed proof. Use OIDC when you want SparkVault-owned refresh rotation, revocation, introspection, and logout.
Browser Support
The SDK supports all modern browsers:
- Chrome 80+
- Firefox 75+
- Safari 13.1+
- Edge 80+
Note: Passkey authentication requires WebAuthn support. On unsupported browsers, passkey will not appear as an option — other methods will still work.
Build & Release
Setup
git clone https://github.com/Spark-Vault/sdk-js.git
cd sdk-js
npm installScripts
npm run build # Build all bundles
npm run build:watch # Build with watch mode
npm run typecheck # Run TypeScript type checking
npm run lint # Run ESLint
npm run test # Run tests
npm run test:watch # Run tests in watch modeProject Structure
sdk-js/
├── src/
│ ├── index.ts # Main entry point
│ ├── config.ts # Configuration management
│ ├── http.ts # HTTP client
│ ├── errors.ts # Error types
│ └── identity/ # Identity module
├── dist/ # Built bundles
├── tests/ # Test files
├── .github/workflows/ # CI/CD workflows
├── rollup.config.js # Rollup bundler config
├── tsconfig.json # TypeScript config
└── package.jsonLicense
MIT License - see LICENSE for details.
