OAuth Integration
Security & Data Integrity
How Euler Stream OAuth protects user sessions with envelope encryption and zero-knowledge architecture.
Security is at the core of the Euler Stream OAuth system. This document explains our security architecture, why your users' TikTok sessions are protected even in worst-case scenarios, and what this means for your integration.
Zero-Knowledge Session Storage
When a user authenticates via QR code, their TikTok session credentials are never stored in plaintext — not in our database, not in logs, not anywhere. Here's why this matters:
The Problem with Traditional Storage
In a typical OAuth system, session credentials are stored in a database, possibly encrypted with a master key. If an attacker gains database access and the master key, they can decrypt all user sessions.
Our Solution: Envelope Encryption
Euler Stream uses envelope encryption where the decryption keys are derived from the tokens themselves:
flowchart TD
A["TikTok Session ID<br/>(sensitive)"] --> B["Encrypt with AES-GCM"]
DEK["Data Encryption Key<br/>(DEK)"] --> B
B --> C["Encrypted Session Blob<br/>(stored in database)"]
DEK --> W1["Wrap DEK with<br/>Access Token"]
DEK --> W2["Wrap DEK with<br/>Refresh Token"]
W1 --> S["Stored alongside session"]
W2 --> S
S -. "Either token can<br/>independently unwrap<br/>the DEK & decrypt" .-> AWhat This Means
-
Database breach = useless data: If an attacker compromises our database, they get encrypted blobs that cannot be decrypted without the corresponding tokens (which are only held by your application).
-
Zero-knowledge architecture: Even we, the operators of Euler Stream, cannot decrypt user sessions. We do not have access to the tokens stored in your application.
-
Decryption only in memory: The session ID only exists in decrypted form in RAM during active API requests. It is never written to disk, logs, or persistent storage in plaintext.
-
Per-session isolation: Each user's session has a unique DEK. Compromising one doesn't affect others.
-
Dual-key recovery: Both the access token and refresh token can independently decrypt the session. This means refreshing your access token doesn't break session access, and API requests work with just the access token.
Security Implications for Your Application
Because of this architecture, you are responsible for secure token storage:
Token Security Best Practices
// ❌ BAD: Storing tokens in plaintext
await db.users.update({
id: userId,
tiktokAccessToken: tokens.access_token,
tiktokRefreshToken: tokens.refresh_token,
});
// ✅ GOOD: Encrypt tokens at rest
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
const ENCRYPTION_KEY = process.env.TOKEN_ENCRYPTION_KEY!; // 32 bytes
function encryptToken(token: string): string {
const iv = randomBytes(16);
const cipher = createCipheriv('aes-256-gcm', Buffer.from(ENCRYPTION_KEY, 'hex'), iv);
let encrypted = cipher.update(token, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}
function decryptToken(encryptedData: string): string {
const [ivHex, authTagHex, encrypted] = encryptedData.split(':');
const decipher = createDecipheriv(
'aes-256-gcm',
Buffer.from(ENCRYPTION_KEY, 'hex'),
Buffer.from(ivHex, 'hex')
);
decipher.setAuthTag(Buffer.from(authTagHex, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
Additional Security Measures
- Use environment variables for client secrets, never commit them
- Implement rate limiting on your OAuth callback endpoint
- Validate the
stateparameter to prevent CSRF attacks - Use HTTPS everywhere (required for redirect URIs except localhost)
- Rotate your client secret periodically via the dashboard
- Audit access logs to detect suspicious activity
What Happens If...
Your Database Is Breached
If your application's database is compromised and tokens are stolen:
- The attacker can use the tokens to make API requests as your users
- Immediately regenerate your client secret in the Euler Stream dashboard
- Revoke compromised tokens via the revoke endpoint
- Users will need to re-authorize
Euler Stream's Database Is Breached
If Euler Stream's database is compromised:
- Attackers get encrypted session blobs
- These cannot be decrypted without your users' tokens
- No user sessions are compromised
- No action required from you (but we'd notify you anyway)
A User's Tokens Are Leaked
If a specific user's tokens are leaked:
- Revoke those tokens immediately via the revoke endpoint
- The user must re-authorize
- Other users are unaffected
Token Transmission Security
HTTPS Requirement
All OAuth endpoints use HTTPS. Redirect URIs must also use HTTPS in production (localhost is exempt for development convenience, per RFC 8252).
Logging and Debugging
What We Log
- Request metadata (timestamp, endpoint, response code)
- Client ID (to identify your application)
- Error messages (without sensitive data)
What We Never Log
- Access tokens or refresh tokens
- Session IDs (encrypted or plaintext)
- QR code contents
- User credentials of any kind
Your Logging
Ensure your application doesn't accidentally log tokens:
// ❌ BAD: Logging the full response
console.log('Token response:', tokenResponse);
// ✅ GOOD: Log only safe data
console.log('Token exchange successful', {
expiresIn: tokenResponse.expires_in,
scopes: tokenResponse.scopes,
});
Security Summary
| Layer | Protection |
|---|---|
| Session Storage | Envelope encryption with per-session DEK |
| Database Breach (Euler Stream) | Sessions unrecoverable without client tokens |
| Database Breach (Your App) | Depends on your encryption implementation |
| Developer Access | Zero-knowledge: we cannot access user sessions |
| Token Recovery | Either access OR refresh token can decrypt session |
| In Memory | Sessions decrypted only in RAM during requests |
Reporting Security Issues
If you discover a security vulnerability:
- Do not disclose it publicly
- Email security concerns to the development team
- Include steps to reproduce
- We'll respond within 48 hours