Holochain Signing Service
An optional service that signs Holochain actions on behalf of your users.
The Flowsta Signing Service allows applications to request cryptographic signatures using users' Flowsta agent keys. This is useful for Holochain apps that want to share a common identity across apps, and for non-Holochain apps that want cryptographic signing capabilities.
Private Keys Never Leave Flowsta
Your application never receives users' private signing keys. When you request a signature, Flowsta's Holochain conductor signs the data and returns only the signature. The private keys remain securely stored on Flowsta's infrastructure and are never exposed through the API.
This Service is Optional
Holochain developers can still use their own agent keys and Holochain infrastructure. The signing service is an additional option, not a requirement.
Why Use the Signing Service?
For Holochain Developers
| Benefit | Description |
|---|---|
| Shared Identity | Users have the same agent key across all participating apps |
| No Conductor Setup | Don't need to run your own Holochain conductor |
| Built-in Consent | Users explicitly grant signing permission |
| Audit Trail | Users can see which apps have signed on their behalf |
For Non-Holochain Developers
| Benefit | Description |
|---|---|
| Cryptographic Signing | Sign arbitrary data with user's agent key |
| Verify Authenticity | Prove data was created by a specific user |
| Audit Trails | Create cryptographically verifiable records |
| No Crypto Expertise | Flowsta handles the key management |
How It Works
The signing service acts as a secure intermediary. Your app sends data to be signed, Flowsta's Holochain conductor performs the signing using the user's private key, and only the signature is returned. Private keys never leave the conductor.
sequenceDiagram
participant User
participant YourApp as Your App
participant Flowsta as Flowsta Auth
participant HC as Holochain Conductor
User->>YourApp: Uses your app
YourApp->>Flowsta: Request signing with OAuth token
Flowsta->>Flowsta: Check signing permission
Flowsta->>HC: Sign action with user's agent key
Note over HC: Private key stays here
HC->>Flowsta: Return signature only
Flowsta->>YourApp: Return signature + public key
Note over YourApp: Never receives private key
YourApp->>User: Action completed!What You Receive
| Data | Returned | Description |
|---|---|---|
| Signature | ✅ Yes | Cryptographic proof the user signed the data |
| Public Key | ✅ Yes | Identifies which user signed (can be shared publicly) |
| Private Key | ❌ Never | Remains secure on Flowsta's Holochain conductor |
Installation
npm install @flowsta/auth @flowsta/holochainQuick Start
1. Create an OAuth Application
- Go to dev.flowsta.com
- Create a new application
- Enable the
holochain:signscope in Holochain Integration settings - Add your redirect URIs
- Note your client ID
2. Set Up Authentication with Signing Scope
import { FlowstaAuth } from '@flowsta/auth';
import { FlowstaHolochain, createHolochainClient } from '@flowsta/holochain';
// Configure auth with holochain:sign scope
const auth = new FlowstaAuth({
clientId: 'your-client-id',
redirectUri: 'https://yourapp.com/callback',
scopes: ['openid', 'public_key', 'holochain:sign'] // Request signing permission
});
// Create Holochain client
const holochain = createHolochainClient(auth);3. Authenticate Users
// Redirect to login (user will see consent screen with signing permission)
auth.login();
// Handle callback
const user = await auth.handleCallback();
console.log('User agent key:', user.agentPubKey);
console.log('User DID:', user.did);4. Sign Holochain Actions
try {
const result = await holochain.signAction({
action: {
type: 'CreateEntry',
entry_type: 'message',
payload: {
content: 'Hello, Holochain!',
timestamp: Date.now()
}
},
reason: 'Creating a new message'
});
console.log('Signed action:', result.signedAction);
console.log('Signature:', result.signature);
console.log('Agent key:', result.agentPubKey);
} catch (error) {
if (error instanceof ConsentRequired) {
// User hasn't granted signing permission
// Re-authenticate with holochain:sign scope
auth.login();
}
}OAuth Scopes
When using the signing service, request these scopes:
| Scope | Description | Required |
|---|---|---|
openid | User identifier | ✅ Yes |
public_key | User's Holochain agent public key | Recommended |
holochain:sign | Permission to sign Holochain actions | ✅ Yes |
did | User's W3C DID | Optional |
Important
The holochain:sign scope triggers a special consent screen that clearly explains to users that your app will be able to sign Holochain actions on their behalf. Make sure your application name and logo are set up correctly in the developer portal.
API Reference
signAction(request)
Sign a Holochain action.
Request:
interface SignActionRequest {
/** The Holochain action to sign */
action: Record<string, unknown>;
/** Human-readable reason (shown in audit log) */
reason?: string;
}Response:
interface SignActionResponse {
success: boolean;
signedAction: Record<string, unknown>;
signature: string; // Base64-encoded signature
agentPubKey: string; // User's agent public key
did: string; // User's DID
signedAt: string; // ISO timestamp
}Example:
const result = await holochain.signAction({
action: {
type: 'CreateEntry',
entry_type: 'post',
payload: { title: 'Hello World', content: '...' }
},
reason: 'Publishing a blog post'
});signBytes(request)
Sign raw bytes for custom operations. This is useful for non-Holochain apps.
Request:
interface SignBytesRequest {
/** Base64-encoded bytes to sign */
bytes: string;
/** Human-readable reason */
reason?: string;
}Response:
interface SignBytesResponse {
success: boolean;
signature: string; // Base64-encoded signature
agentPubKey: string; // User's agent public key
}Example:
// Encode your data as base64
const bytesToSign = btoa(JSON.stringify(myCustomData));
const result = await holochain.signBytes({
bytes: bytesToSign,
reason: 'Signing custom data'
});getPermissions()
Get all apps the user has granted signing permission to.
const permissions = await holochain.getPermissions();
// Returns:
// [{
// id: 'uuid',
// appId: 'uuid',
// appName: 'My App',
// appLogo: 'https://...',
// scopes: ['holochain:sign'],
// grantedAt: '2025-01-15T10:00:00Z',
// lastUsedAt: '2025-01-15T10:30:00Z',
// signCount: 42
// }]revokePermission(appId)
Revoke signing permission for an app.
await holochain.revokePermission('app-uuid');hasSigningPermission()
Check if the user has granted signing permission to your app.
const hasPermission = await holochain.hasSigningPermission();
if (!hasPermission) {
// Prompt user to grant permission
}Error Handling
ConsentRequired
Thrown when the user hasn't granted holochain:sign permission.
import { ConsentRequired, FlowstaHolochainError } from '@flowsta/holochain';
try {
await holochain.signAction({ action: myAction });
} catch (error) {
if (error instanceof ConsentRequired) {
// Re-authenticate with holochain:sign scope
console.log('Required scope:', error.requiredScope);
auth.login();
} else if (error instanceof FlowstaHolochainError) {
console.error('Error code:', error.code);
console.error('Description:', error.description);
}
}Common Error Codes
| Code | Description |
|---|---|
consent_required | User must grant holochain:sign permission |
unauthorized | Access token missing or invalid |
insufficient_scope | Token doesn't have holochain:sign scope |
signing_failed | Holochain conductor error |
server_error | Internal server error |
User Experience
Consent Screen
When users authenticate with the holochain:sign scope, they see a clear consent screen that:
- Shows your app's name and logo
- Clearly explains what "Sign Holochain Actions" means
- Highlights this as a sensitive permission (displayed with amber warning styling)
- Links to your privacy policy
TIP
Make sure your app's name and logo are configured in the Developer Portal for the best user experience.
User Dashboard
Users can manage signing permissions from their Flowsta dashboard:
- View all apps with signing permission
- See signing activity (when, what was signed)
- Revoke permissions at any time
Non-Holochain Usage
If you're not building a Holochain app but want cryptographic signing:
import { FlowstaAuth } from '@flowsta/auth';
import { createHolochainClient } from '@flowsta/holochain';
const auth = new FlowstaAuth({
clientId: 'your-client-id',
redirectUri: 'https://yourapp.com/callback',
scopes: ['openid', 'holochain:sign']
});
const signer = createHolochainClient(auth);
// Sign any data
const data = { documentId: '123', approvedBy: user.id, timestamp: Date.now() };
const dataBytes = btoa(JSON.stringify(data));
const result = await signer.signBytes({
bytes: dataBytes,
reason: 'Approving document #123'
});
// Store the signature alongside your data
const signedDocument = {
...data,
signature: result.signature,
signerPubKey: result.agentPubKey
};Use Cases for Non-Holochain Apps
- Document signing - Prove a user approved a document
- Audit logs - Create cryptographically verifiable records
- Data integrity - Sign data to detect tampering
- Multi-party workflows - Collect multiple signatures
Security Model
The signing service is designed with security as the top priority:
Private Keys Are Never Exposed
┌─────────────────────────────────────────────────────────────────┐
│ FLOWSTA INFRASTRUCTURE │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Holochain Conductor │ │
│ │ │ │
│ │ 🔐 Private Keys (stored securely, never transmitted) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ Signs data internally │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Auth API │ │
│ │ Returns: signature + public key only │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
HTTPS Response
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ YOUR APPLICATION │
│ │
│ ✅ Receives: signature, public key, timestamp │
│ ❌ Never receives: private key │
│ │
└─────────────────────────────────────────────────────────────────┘Trust Model
| Party | Access Level |
|---|---|
| User | Owns the key, grants/revokes signing permission |
| Flowsta | Custodian of keys, executes signing on user request |
| Your App | Receives signatures only, cannot sign without user consent |
User Controls
Users maintain full control over their signing permissions:
- View all apps with signing permission in their dashboard
- See detailed signing activity logs
- Revoke permissions instantly at any time
- Receive consent warnings before granting
holochain:sign
Security Best Practices
1. Request Only What You Need
Only request holochain:sign if your app actually needs to sign actions. For read-only apps, public_key is sufficient.
2. Provide Reasons
Always include a reason when signing:
await holochain.signAction({
action: myAction,
reason: 'Publishing comment on post #123' // Shown in user's audit log
});3. Handle Errors Gracefully
try {
await holochain.signAction({ action });
} catch (error) {
if (error instanceof ConsentRequired) {
// Politely ask user to grant permission
showModal('This action requires signing permission. Click to authorize.');
}
}4. Respect Token Expiration
Access tokens expire. Check before signing:
if (!auth.isAuthenticated()) {
auth.login();
return;
}
await holochain.signAction({ action });Migration from Direct Holochain
If you're migrating from a direct Holochain integration:
Before (Direct Holochain):
import { AppWebsocket } from '@holochain/client';
const client = await AppWebsocket.connect('ws://localhost:8888');
await client.callZome({
cell_id: myCellId,
zome_name: 'my_zome',
fn_name: 'create_entry',
payload: data
});After (Flowsta Signing Service):
import { FlowstaAuth } from '@flowsta/auth';
import { createHolochainClient } from '@flowsta/holochain';
const auth = new FlowstaAuth({ clientId, redirectUri, scopes: ['openid', 'holochain:sign'] });
const holochain = createHolochainClient(auth);
// User authenticates via OAuth
auth.login();
// Later, sign actions
const signed = await holochain.signAction({ action: data });Related Documentation
- Holochain Overview - How Holochain powers Flowsta
- SDK Reference - Full @flowsta/auth documentation
- OAuth Flow - Understand the authentication flow
- API Reference - REST API endpoints
- Security Guide - Best practices
Need Help?
- 💬 Discord: Join our community
- 🆘 Support: Find out about Flowsta support options
- 🐙 GitHub: github.com/WeAreFlowsta