Authentication
SEA provides a robust authentication system based on cryptographic key pairs. Users authenticate by proving ownership of their private keys.
Authentication Methods
There are three ways to authenticate in SEA:
- Alias + Password: Traditional username/password
- Key Pair: Direct authentication with cryptographic keys
- Session Recall: Restore from sessionStorage
Alias and Password Authentication
The most common authentication method for end users.
Flow Overview
Reference: ~/workspace/source/sea/auth.js:43-96
user.auth(alias, password, callback)
Step-by-step process:
- User Lookup: Fetch user data from
~@alias node
- Extract Credentials: Parse the
auth field containing {ek, s}
- Derive Key: Use PBKDF2 to derive decryption key from password + salt
- Decrypt Private Keys: Decrypt the encrypted key bundle
- Reconstruct Pair: Combine public and decrypted private keys
- Set Session: Store credentials in memory
- Emit Event: Fire ‘auth’ event
Example
const gun = Gun();
const user = gun.user();
user.auth('alice', 'MySecurePassword123', (ack) => {
if (ack.err) {
console.error('Authentication failed:', ack.err);
return;
}
console.log('Authenticated as:', user.is.alias);
console.log('Public key:', user.is.pub);
});
Error Handling
user.auth('alice', 'wrongpassword', (ack) => {
if (ack.err) {
// Common errors:
// "Wrong user or password."
// "User cannot be found!"
// "Your user account is not published for dApps to access..."
if (ack.wait) {
console.log('Auth already in progress, please wait');
}
}
});
Key Pair Authentication
Direct authentication using a key pair object. More reliable than alias/password.
With Complete Key Pair
const pair = {
pub: 'signing_public_key',
priv: 'signing_private_key',
epub: 'encryption_public_key',
epriv: 'encryption_private_key'
};
user.auth(pair, (ack) => {
console.log('Authenticated with full keypair');
});
With Public Keys Only
If only public keys are provided, SEA will attempt to load and decrypt the private keys:
const partialPair = {
pub: 'signing_public_key',
epub: 'encryption_public_key'
};
// SEA will look up the user and prompt for password
user.auth(partialPair, 'password', (ack) => {
console.log('Authenticated and decrypted private keys');
});
Session Management
Persistent Sessions
Store authenticated session in sessionStorage:
user.auth('alice', 'password', (ack) => {
// Session stored in sessionStorage
}, { remember: true });
Session Recall
Restore a session on page load:
const gun = Gun();
const user = gun.user();
// Attempt to restore session
user.recall({ sessionStorage: true });
gun.on('auth', () => {
console.log('Session restored for:', user.is.alias);
});
Session Data Structure
Stored in sessionStorage:
{
recall: true,
pair: JSON.stringify({
pub: '...',
priv: '...',
epub: '...',
epriv: '...'
})
}
SessionStorage persists only for the current tab session. It’s cleared when the tab closes. For persistent login across sessions, implement your own secure storage mechanism.
Authentication State
Checking Authentication
if (user.is) {
console.log('User is authenticated');
console.log('Alias:', user.is.alias);
console.log('Public Key:', user.is.pub);
console.log('Encryption Key:', user.is.epub);
} else {
console.log('No user authenticated');
}
Authentication Events
// Listen for authentication
gun.on('auth', (ack) => {
console.log('User authenticated!');
console.log('Sea credentials:', ack.sea);
});
// Authenticate triggers the event
user.auth('alice', 'password');
Password Updates
Change a user’s password during authentication:
user.auth('alice', 'OldPassword123', (ack) => {
if (ack.err) {
console.error('Cannot change password:', ack.err);
return;
}
console.log('Password successfully changed!');
}, { change: 'NewPassword456' });
Process:
- Authenticate with old password
- Generate new salt
- Derive new key from new password + new salt
- Re-encrypt private keys with new derived key
- Update
auth field in user graph
Reference: ~/workspace/source/sea/auth.js:108-127
Retry Logic
Authentication includes automatic retry for distributed systems:
user.auth('alice', 'password', callback, {
retries: 9 // Default: 9 retries
});
Retries help when:
- User data hasn’t fully propagated across peers
- Network delays in distributed networks
- Data is being synced from remote peers
Security Model
Key Storage
Never stored in plaintext:
- Private signing key (priv)
- Private encryption key (epriv)
Always encrypted with:
- PBKDF2-derived key (from password + salt)
- AES-GCM encryption
- 100,000 iterations (configurable)
Memory Security
Reference: ~/workspace/source/sea/auth.js:75-76
// Credentials stored in memory only
user.is = {
pub: pair.pub,
epub: pair.epub,
alias: alias || pair.pub
};
user._.sea = pair; // Full keypair in private scope
Session Security
SessionStorage is used only if explicitly enabled:
if (SEA.window && opt.remember) {
SEA.window.sessionStorage.recall = true;
SEA.window.sessionStorage.pair = JSON.stringify(pair);
}
SessionStorage stores the decrypted key pair. Only use remember: true in trusted environments. Consider implementing additional encryption for the sessionStorage data in production applications.
Advanced Authentication
Plugin-based Authentication
SEA supports custom authentication plugins via SEA.name:
user.auth(); // No params triggers plugin flow
// Implement custom plugin
SEA.name = async (callback) => {
// Custom authentication logic
// e.g., OAuth, biometrics, hardware keys
callback('custom_user_identifier');
};
Multi-device Authentication
Authenticate the same user across multiple devices:
// Device 1: Create user and export keys
user.create('alice', 'password', async (ack) => {
const exportedPair = await user._.sea;
// Securely transfer 'exportedPair' to Device 2
});
// Device 2: Import and authenticate
user.auth(importedPair, (ack) => {
console.log('Same user, different device');
});
Best Practices
- Always use callbacks to handle async authentication
- Check
user.is before performing authenticated operations
- Handle errors gracefully - network issues, wrong passwords, missing users
- Use session recall for better UX, but understand security implications
- Implement logout - call
user.leave() when user exits
- Validate passwords - enforce minimum 8 characters (SEA’s default)
- Use HTTPS - required for Web Crypto API in browsers
Common Patterns
Auto-authenticate on Load
const gun = Gun();
const user = gun.user().recall({ sessionStorage: true });
gun.on('auth', () => {
console.log('User ready:', user.is.alias);
loadUserData();
});
Protected Routes
function requireAuth(callback) {
if (!user.is) {
redirectToLogin();
return;
}
callback();
}
requireAuth(() => {
user.get('private-data').once(data => {
console.log(data);
});
});
Graceful Logout
function logout() {
user.leave();
clearUIState();
redirectToLogin();
}