Documentation Index Fetch the complete documentation index at: https://docs.grainql.com/llms.txt
Use this file to discover all available pages before exploring further.
Authentication Strategies
Choose the right strategy for your security needs:
NONE : No authentication, anyone with tenant ID can send events
Use for: Public websites, demos, development
Security level: Low
SERVER_SIDE : Secret key authentication
Use for: Backend services, server-side code
Security level: High
Never expose secret in client code
JWT : Token-based authentication
Use for: Client apps with user authentication
Security level: High
Validates user identity with each request
Learn more in Authentication .
User ID Security
JWT User ID Validation
With JWT auth, user IDs must match the token’s subject:
// JWT payload: { sub: "user_123" }
grain . setUserId ( 'user_123' ); // ✅ Allowed
grain . track ( 'event' );
grain . setUserId ( 'user_456' ); // ❌ Blocked - doesn't match JWT
grain . track ( 'event' ); // Request rejected
Why : Prevents users from impersonating others.
User ID Override Restrictions
Be careful with userId overrides:
// ❌ Risky: Setting properties for many different users
await grain . setProperty ({ plan: 'premium' }, { userId: 'user_1' });
await grain . setProperty ({ plan: 'premium' }, { userId: 'user_2' });
await grain . setProperty ({ plan: 'premium' }, { userId: 'user_3' });
// ... many more users
// May trigger rate limiting!
When using overrides :
Ensure you have permission to modify those users
Avoid switching between many different user IDs
With JWT, user ID must match token subject
Rate Limiting
Grain may block requests if:
Too many distinct user IDs from same source
Unusual patterns detected
Excessive property updates
Best practice : Use global setUserId() for current user, avoid overrides unless necessary.
Secret Key Protection
Never Expose Secrets
// ❌ NEVER DO THIS - Secret in client code
const grain = createGrainAnalytics ({
tenantId: 'your-tenant-id' ,
authStrategy: 'SERVER_SIDE' ,
secretKey: 'sk_live_abc123...' // Exposed in bundle!
});
// ✅ Correct - Secret in environment variable
const grain = createGrainAnalytics ({
tenantId: 'your-tenant-id' ,
authStrategy: 'SERVER_SIDE' ,
secretKey: process . env . GRAIN_SECRET_KEY // Safe on server
});
Server-Side Only
Use SERVER_SIDE auth only in:
Node.js backends
Serverless functions
Server API routes
Never in:
Browser JavaScript
Mobile apps
Any client-side code
Data Privacy
Sensitive Data
Never track sensitive information:
// ❌ BAD - Sensitive data
grain . track ( 'user_action' , {
password: user . password ,
ssn: user . ssn ,
credit_card: user . card
});
// ✅ Good - Non-sensitive data
grain . track ( 'user_action' , {
action_type: 'profile_update' ,
section: 'settings'
});
User Properties
Only set properties users consent to:
// With user consent
await grain . setProperty ({
email: user . email ,
location: user . country ,
preferences: user . preferences
});
Follow GDPR, CCPA, and other privacy regulations.
CORS and API Security
Grain API uses CORS to restrict access. Configure allowed origins in your dashboard at grainql.com/dashboard .
Default : All origins allowed (*)
Recommended : Whitelist specific domains
Allowed origins:
- https://yourapp.com
- https://www.yourapp.com
- http://localhost:3000 (development)
Token Refresh
For JWT authentication, ensure tokens stay fresh:
const authProvider = {
async getToken () {
// Get fresh token (handles refresh automatically)
const token = await auth0 . getAccessTokenSilently ();
return token ;
}
};
Don’t cache tokens yourself - let your auth provider handle refresh.
Secure Configuration
Environment-Based Auth
Use different auth for different environments:
const grain = createGrainAnalytics ({
tenantId: 'your-tenant-id' ,
authStrategy: process . env . NODE_ENV === 'production'
? 'JWT'
: 'NONE' ,
authProvider: process . env . NODE_ENV === 'production'
? jwtAuthProvider
: undefined
});
Development: No auth (easier)
Production: JWT (secure)
Content Security Policy (CSP)
If using CSP headers, allow Grain API:
Content-Security-Policy:
connect-src 'self' https://clientapis.grainql.com;
XSS Protection
Never render user-provided config values as HTML:
const heroText = grain . getConfig ( 'hero_text' );
// ❌ Dangerous - XSS risk
element . innerHTML = heroText ;
// ✅ Safe - Escaped
element . textContent = heroText ;
// ✅ Safe - React escapes automatically
return < h1 >{ heroText } </ h1 > ;
Audit Logging
Track security-relevant events:
// Track login attempts
await grain . trackLogin ({
method: 'email' ,
success: loginSuccess ,
ip_address: req . ip // Don't track unless needed
});
// Track permission changes
grain . track ( 'permission_changed' , {
user_id: targetUser ,
old_role: oldRole ,
new_role: newRole
});
Best Practices
1. Principle of Least Privilege : Grant minimum necessary permissions
2. Rotate Secrets : Regularly rotate secret keys
3. Monitor Suspicious Activity : Check dashboard for unusual patterns
4. Validate Input : Sanitize data before tracking
5. Use HTTPS : Always use HTTPS in production
6. Regular Updates : Keep SDK updated for security patches
Next Steps
Authentication Choose the right auth strategy
Configuration Configure securely