LastID Rust SDK
Rust SDK for integrating with the LastID Identity Provider (IDP) to request and verify credentials from users.
Dual-target: Works on both native (tokio) and browser (WASM) from a single crate.
Features
- Type-safe policy builders with compile-time validation
- 8 credential types: Base, Persona, VerifiedEmail, VerifiedPhone, VerifiedPersona, Employment, Trust, AgeProof
- DPoP authentication (RFC 9449) for proof-of-possession
- Trust registry validation with 60-second caching
- Dual-target support: Native (tokio) + WASM (browser)
- Structured errors with error codes for programmatic handling
- TypeScript definitions for excellent IDE support
Installation
Rust (Native)
[]
= "0.1.0"
= { = "1", = ["full"] }
JavaScript / TypeScript (WASM)
Or build from source:
# Install wasm-pack if you haven't
# Build WASM package with TypeScript bindings
# Output in pkg/ directory:
# - lastid_sdk.js (JavaScript bindings)
# - lastid_sdk.d.ts (TypeScript definitions - auto-generated)
# - lastid_sdk_bg.wasm (WASM binary)
Rust (WASM target)
[]
= { = "0.1.0", = false, = ["wasm"] }
= "0.4"
Quick Start (Rust)
use ;
async
Quick Start (JavaScript / TypeScript)
import init, {
WasmConfig,
WasmClient,
BaseCredentialPolicy,
generateState,
generateNonce
} from '@lastid/sdk';
// Initialize WASM module
await init();
// Create configuration
const config = new WasmConfig(
'https://human.lastid.co', // IDP endpoint
'your-client-id' // OAuth client ID
);
// Create client
const client = new WasmClient(config);
// Build policy with fluent API
const policy = new BaseCredentialPolicy()
.withState(generateState())
.withNonce(generateNonce())
.withCallback(window.location.href);
try {
// Request credential
const response = await client.requestCredential(policy);
console.log('Request ID:', response.requestId);
console.log('QR Code URI:', response.requestUri);
// Subscribe for real-time updates (WebSocket with polling fallback)
const status = await client.subscribeForCompletion(response.requestId);
if (status.status === 'fulfilled') {
// Verify the presentation
const credential = await client.verifyPresentation(status.presentation!);
console.log('Subject DID:', credential.subjectDid);
console.log('Claims:', credential.claims);
}
} catch (error) {
// Structured error handling
if (error.code === 'NETWORK_ERROR' && error.isRetryable) {
console.log(`Retry in ${error.suggestedRetryMs}ms`);
} else if (error.code === 'POLICY_ERROR') {
console.error('Policy validation failed:', error.details);
}
}
Error Handling (TypeScript)
The SDK provides structured errors with codes for programmatic handling:
import type { LastIDError, ErrorCode } from '@lastid/sdk';
try {
await client.requestCredential(policy);
} catch (error) {
const e = error as LastIDError;
switch (e.code) {
case 'CONFIG_ERROR':
// Invalid configuration
break;
case 'NETWORK_ERROR':
// Connection failed - check e.isRetryable
if (e.isRetryable) {
await delay(e.suggestedRetryMs ?? 1000);
// retry...
}
break;
case 'POLICY_ERROR':
// Invalid policy - check e.details
console.error(e.details);
break;
case 'RATE_LIMIT_ERROR':
// Too many requests - wait suggestedRetryMs
break;
case 'VERIFICATION_ERROR':
// Credential verification failed
break;
case 'TRUST_ERROR':
// Issuer not in trust registry
break;
default:
console.error('Unexpected error:', e.message);
}
}
Available Error Codes
| Code | Description | Retryable |
|---|---|---|
CONFIG_ERROR |
Invalid endpoint, missing client_id | No |
NETWORK_ERROR |
Connection failed, timeout, DNS | Yes (1s) |
AUTH_ERROR |
Invalid credentials, token expired | No |
POLICY_ERROR |
Missing fields, invalid constraints | No |
VERIFICATION_ERROR |
Signature invalid, expired, revoked | No |
TRUST_ERROR |
Issuer not found, suspended | No |
NOT_FOUND_ERROR |
Request not found (404) | No |
EXPIRED_ERROR |
Credential request timed out | No |
DENIED_ERROR |
User denied the request | No |
RATE_LIMIT_ERROR |
Too many requests | Yes (30s) |
INTERNAL_ERROR |
Unexpected SDK error | No |
Configuration
Create lastid.toml in your project root:
= "https://human.lastid.co"
[]
= 3
= 1000
[]
= 2000
= 300
[]
= true
= 60
Or use environment variables:
Precedence: Env vars > Explicit config > Discovered TOML > Defaults
Documentation
- API Reference
- Configuration Example - Complete configuration reference
Feature Flags
# Default features
= ["base-policy"]
# All credential policies
= ["base-policy", "persona-policy", "verified-email-policy",
"verified-phone-policy", "verified-persona-policy",
"employment-policy", "trust-policy", "age-proof-policy", "tracing"]
# WASM support (browser)
= ["wasm-bindgen", "wasm-bindgen-futures", "web-sys", "js-sys"]
# WebSocket support for real-time status updates
= ["futures-util", "tokio-tungstenite"]
# Enable DPoP keypair serialization for serverless persistence (SEC-002)
# WARNING: Serialized keypairs contain private keys - ensure encrypted storage
= []
Serverless Keypair Persistence
For serverless deployments where you need to persist the DPoP keypair across invocations, enable the keypair-serialization feature:
[]
= { = "0.1.0", = ["keypair-serialization"] }
use DPoPKeyPair;
// Generate and serialize (first invocation)
let keypair = generate?;
let serialized = to_string?;
// Store `serialized` in your secrets manager (AWS KMS, Vault, etc.)
// Deserialize (subsequent invocations)
let restored: DPoPKeyPair = from_str?;
Security Warning: The serialized keypair contains the private key. Always store encrypted (e.g., AWS Secrets Manager, HashiCorp Vault).
Development
# Run tests (native)
# Run tests (WASM) - requires wasm-pack
# Lint
# Format
# Security audit
Security
See SECURITY.md for vulnerability reporting.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT License (LICENSE-MIT)
at your option.
Contributing
See CONTRIBUTING.md for guidelines.