Security
This guide covers security considerations when building with InAppAI React, helping you create secure AI-powered applications.
Security Model
InAppAI React uses a backend-first security model:
┌──────────────────────────────┐
│ Browser (Public) │
│ │
│ • No API keys │
│ • No secrets │
│ • JWT auth only │
│ • Tool handlers run here │
└──────────────┬───────────────┘
│ HTTPS
↓
┌──────────────────────────────┐
│ Backend (Protected) │
│ │
│ • API keys stored │
│ • Validates all requests │
│ • Rate limiting │
│ • User authentication │
└──────────────┬───────────────┘
│ API Key
↓
┌──────────────────────────────┐
│ AI Provider API │
└──────────────────────────────┘
Never Expose API Keys
Wrong: API Keys in Client
// NEVER DO THIS - API key exposed in browser!
<InAppAI
apiKey="sk_live_abc123..." // Leaked to users!
/>
Anyone can view source code, steal your API key, run up your bill, and access your AI account.
Correct: Backend Proxy
// Frontend - No API keys
<InAppAI
agentId="your-agent-id" // Your backend handles the API key
/>
// Backend - API key stored securely
import { OpenAI } from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY, // Server-side only
});
app.post('/api/chat', async (req, res) => {
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: req.body.messages,
});
res.json(response);
});
Authentication
JWT-Based Authentication
Use JWT tokens to identify users:
// Frontend
import { useAuth } from './auth';
function App() {
const { user, token } = useAuth();
if (!user) {
return <LoginPage />;
}
return (
<InAppAI
agentId="your-agent-id"
authToken={token} // JWT sent to backend
context={{
userId: user.id,
userName: user.name,
}}
/>
);
}
// Backend - Verify JWT
import jwt from 'jsonwebtoken';
app.use('/api/chat', (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.replace('Bearer ', '');
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
});
Token Best Practices
- Use Short Expiration
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
- Implement Refresh Tokens
useEffect(() => {
const refreshInterval = setInterval(async () => {
const newToken = await refreshAccessToken();
setToken(newToken);
}, 50 * 60 * 1000); // Refresh every 50 min
return () => clearInterval(refreshInterval);
}, []);
Context Security
Avoid Sensitive Data
Don’t send sensitive data in context:
// Bad - sensitive data exposed
context={{
password: user.password, // Never send
creditCard: user.creditCard, // Never send
ssn: user.ssn, // Never send
}}
// Good - minimal, non-sensitive data
context={{
userId: user.id,
userName: user.name,
userRole: user.role,
}}
Backend Validation
Always validate context on the backend:
app.post('/api/chat', authenticate, (req, res) => {
// Don't trust client context for permissions
// Use verified user from JWT instead
const userId = req.user.id; // From verified JWT
const isAdmin = await checkAdminStatus(userId);
// ...
});
Tool Security
Validate Tool Parameters
const tools: Tool[] = [
{
name: 'deleteItem',
handler: async ({ itemId }) => {
// Verify ownership before deleting
const currentUser = await getCurrentUser();
const item = await getItem(itemId);
if (item.ownerId !== currentUser.id) {
return { success: false, error: 'Unauthorized' };
}
await deleteItem(itemId);
return { success: true };
},
},
];
Rate Limit Tools
const toolCallCounts = new Map();
{
name: 'sendEmail',
handler: async ({ to, body }) => {
const userId = getCurrentUserId();
const count = toolCallCounts.get(userId) || 0;
if (count >= 10) {
return { success: false, error: 'Rate limit exceeded' };
}
await sendEmail(to, body);
toolCallCounts.set(userId, count + 1);
return { success: true };
},
}
HTTPS Requirements
Always use HTTPS in production:
// Production - HTTPS only
<InAppAI endpoint="https://api.yourapp.com" />
// Development only - HTTP acceptable
<InAppAI endpoint="http://localhost:3001" />
CORS Configuration
Restrict origins:
import cors from 'cors';
// Dangerous - allows all origins
app.use(cors({ origin: '*' }));
// Safe - specific origins only
app.use(cors({
origin: [
'https://yourapp.com',
'https://www.yourapp.com',
],
credentials: true,
}));
Rate Limiting
Backend Rate Limiting
import rateLimit from 'express-rate-limit';
const chatLimiter = rateLimit({
windowMs: 60 * 1000,
max: 20,
message: 'Too many requests, please try again later',
keyGenerator: (req) => req.user.id,
});
app.post('/api/chat', authenticate, chatLimiter, chatHandler);
Security Checklist
- API keys stored server-side only (never in client)
- HTTPS enabled in production
- JWT authentication implemented
- Token expiration set (≤1 hour)
- CORS restricted to specific origins
- Input validation on all endpoints
- Rate limiting enabled (per user)
- Tool parameters validated
- Sensitive data excluded from context
- Audit logging implemented
Next Steps
- Authentication - Implement JWT auth
- Troubleshooting - Common security issues