This document explains the security model of iaptic's Stripe integration and how it protects your subscription data.
Security Layers
1. Access Keys
graph LR
A[Client] -- Access Key --> B[Iaptic]
B -- Verify --> C[Subscription]
C -- Rotate --> D[New Key]
D -- Return --> A
Access keys are:
- Generated during checkout
- Expire after 30 days
- Automatically rotated
- Subscription-specific
- Stored client-side only
2. API Keys
graph LR
A[Iaptic] -- Secret Key --> B[Stripe API]
C[Client] -- Public Key --> D[Stripe Elements]
Two types of Stripe keys:
- Secret key (server-side only)
- Public key (safe for client-side)
3. Webhook Security
graph LR
A[Stripe] -- Event --> B[Webhook]
B -- Verify Signature --> C[Process]
B -- Invalid --> D[Reject]
Each webhook request:
- Includes signature header
- Uses unique signing secret
- Validates timestamp
- Verifies payload integrity
Key Management
Access Key Format
// Format: ak_[32 random chars]
const ACCESS_KEY_PATTERN = /^ak_[a-zA-Z0-9]{32}$/;
// Example
const validKey = 'ak_1234567890abcdef1234567890abcdef';
Key Storage
// Client-side (correct)
localStorage.setItem('stripe_access_key', accessKey);
// Server-side (avoid)
user.accessKey = accessKey; // Don't store unencrypted
// Server-side (if needed)
user.accessKey = await encrypt(accessKey);
Key Rotation
Keys are rotated when:
- Approaching expiration
- Security requires it
- Subscription changes
- Client requests it
Secure Communication
API Requests
// Correct: Send in request body
fetch('/api/check', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ access_key: accessKey })
});
// Incorrect: Don't send in URL
fetch(`/api/check?access_key=${accessKey}`); // Avoid
Webhook Processing
// Verify webhook signature
const event = stripe.webhooks.constructEvent(
rawBody,
signature,
webhookSecret
);
// Always verify managed_by_iaptic flag
if (event.data.object.metadata?.managed_by_iaptic !== 'true') {
return;
}
Data Protection
Sensitive Data
Never store:
- Credit card numbers
- Full card details
- CVV codes
- Unencrypted access keys
Metadata Usage
// Safe to store in metadata
{
"application_username": "user123",
"features": "premium,api",
"tier": "enterprise"
}
// Never store in metadata
{
"access_key": "ak_xyz", // No!
"password": "secret", // No!
"personal_info": "private" // No!
}
Security Best Practices
1. Access Key Handling
class AccessKeyManager {
// Store key securely
static storeKey(subscriptionId, key) {
if (!this.isValidKey(key)) return;
localStorage.setItem(
`stripe_key_${subscriptionId}`,
key
);
}
// Validate key format
static isValidKey(key) {
return /^ak_[a-zA-Z0-9]{32}$/.test(key);
}
// Clear on logout
static clearKeys() {
Object.keys(localStorage)
.filter(k => k.startsWith('stripe_key_'))
.forEach(k => localStorage.removeItem(k));
}
}
2. Error Handling
// Don't expose internal errors
try {
await validateSubscription();
} catch (error) {
// Log internally
console.error('Validation failed:', error);
// Return safe message
throw new Error('Subscription check failed');
}
3. Version Control
// Always specify API version
const stripe = new Stripe(secretKey, {
apiVersion: '2024-11-20.acacia'
});
Security Checklist
Implementation
- [ ] Use HTTPS everywhere
- [ ] Validate all inputs
- [ ] Handle key rotation
- [ ] Process webhooks securely
- [ ] Store keys safely
- [ ] Use correct API version
Monitoring
- [ ] Watch for failed webhooks
- [ ] Monitor key rotations
- [ ] Track access patterns
- [ ] Log security events
- [ ] Alert on anomalies
Maintenance
- [ ] Update API version
- [ ] Rotate webhook secrets
- [ ] Clean expired keys
- [ ] Audit metadata
- [ ] Review permissions
Common Vulnerabilities
1. Key Exposure
- Logging access keys
- Storing server-side
- Sending in URLs
- Committing to code
2. Webhook Security
- Missing signature verification
- Old API versions
- Replay attacks
- Race conditions
3. Access Control
- Incorrect entitlement strategy
- Missing validation
- Improper key rotation
- Metadata leaks
Authentication Flow
sequenceDiagram
participant C as Client
participant I as Iaptic
participant S as Stripe
C->>I: Create Checkout Session
I->>S: Create Session
S-->>I: Session + Access Key
I-->>C: Redirect URL + Key
C->>S: Complete Payment
S->>I: Webhook Event
I->>I: Validate Key
I-->>C: Confirmation
Access Key Rotation
stateDiagram-v2
[*] --> Active
Active --> Rotating: Update
Rotating --> Transitioning: New Key
Transitioning --> Active: Complete
Transitioning --> Failed: Error
Failed --> Active: Retry