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

  1. Use Short Expiration
const token = jwt.sign(
  { userId: user.id },
  process.env.JWT_SECRET,
  { expiresIn: '1h' }
);
  1. 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