Authentication
Authentication allows you to identify users, personalize responses, and secure access to your AI assistant. The InAppAI backend supports JWT (JSON Web Token) authentication for production use cases.
Overview
Authentication in InAppAI enables:
- User Identification - Know who’s chatting with the AI
- Personalized Responses - AI can reference user-specific data via context
- Access Control - Restrict AI features to authenticated users
- Usage Tracking - Monitor usage per user (individual rate limits)
- Conversation Persistence - Save conversations per user
The authToken Prop
The authToken prop accepts a JWT token for authenticating users with the InAppAI backend:
import { useState } from 'react';
import { InAppAI, Message } from '@inappai/react';
import { useAuth } from './auth'; // Your auth system
function App() {
const [messages, setMessages] = useState<Message[]>([]);
const { user, token } = useAuth();
return (
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={setMessages}
authToken={token}
context={{
userId: user?.id,
userName: user?.name,
}}
/>
);
}
Type Signature
authToken?: string | (() => string | null | undefined);
The prop accepts either:
- String: A static JWT token
- Function: A function that returns the token (called on each request)
Static Token
Pass the token directly when it’s readily available:
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={setMessages}
authToken={user.token}
/>
Dynamic Token (Function)
Use a function when:
- Tokens may be refreshed during the session
- Token retrieval is conditional
- You want to return
nullfor unauthenticated state
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={setMessages}
authToken={() => {
// Return null if not authenticated
if (!isLoggedIn) return null;
// Get fresh token (may have been refreshed)
return localStorage.getItem('authToken');
}}
/>
With Token Refresh
function ChatWithTokenRefresh() {
const [messages, setMessages] = useState<Message[]>([]);
const { getAccessToken } = useAuth();
return (
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={setMessages}
authToken={() => getAccessToken()} // Always gets fresh token
/>
);
}
User Context
In addition to authToken, use the context prop to pass user information that the AI can reference:
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={setMessages}
authToken={token}
context={{
userId: user.id,
userName: user.name,
userEmail: user.email,
userRole: user.role,
}}
/>
The AI can reference this context to provide personalized responses.
User Authentication Pattern
1. Create Auth Context
Store user state securely in your frontend:
// auth.tsx - Using React Context for auth state
import { createContext, useContext, useState, useEffect } from 'react';
interface User {
id: string;
name: string;
email: string;
role: string;
}
interface AuthContextType {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | null>(null);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
// Load from localStorage on mount
useEffect(() => {
const savedUser = localStorage.getItem('user');
if (savedUser) {
setUser(JSON.parse(savedUser));
}
}, []);
const login = async (email: string, password: string) => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const { user } = await response.json();
setUser(user);
localStorage.setItem('user', JSON.stringify(user));
};
const logout = () => {
setUser(null);
localStorage.removeItem('user');
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
};
2. Pass authToken and Context to InAppAI
import { useState } from 'react';
import { InAppAI, Message } from '@inappai/react';
import { useAuth } from './auth';
function ChatApp() {
const [messages, setMessages] = useState<Message[]>([]);
const { user, token } = useAuth();
return (
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={setMessages}
authToken={token}
context={{
userId: user?.id,
userName: user?.name,
userRole: user?.role,
}}
/>
);
}
User-Specific Features
Once you have user context, you can implement user-specific features:
User-Specific Conversations
function UserChat() {
const { user, token } = useAuth();
const [messages, setMessages] = useState<Message[]>([]);
// Load user's conversation from your backend
useEffect(() => {
async function loadConversation() {
const response = await fetch(`/api/users/${user.id}/conversation`);
const { messages } = await response.json();
setMessages(messages);
}
if (user) loadConversation();
}, [user?.id]);
// Save to user-specific conversation
const handleMessagesChange = async (newMessages: Message[]) => {
setMessages(newMessages);
await fetch(`/api/users/${user.id}/conversation`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages: newMessages }),
});
};
return (
<InAppAI
agentId="your-agent-id"
conversationId={`user_${user?.id}`}
messages={messages}
onMessagesChange={handleMessagesChange}
authToken={token}
context={{
userId: user?.id,
userName: user?.name,
}}
/>
);
}
Role-Based Features
function RoleBasedChat() {
const { user, token } = useAuth();
const [messages, setMessages] = useState<Message[]>([]);
// Different tools for different roles
const tools = user?.role === 'admin' ? adminTools : userTools;
return (
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={setMessages}
authToken={token}
tools={tools} // Role-specific tools
context={{
userId: user?.id,
userRole: user?.role,
permissions: user?.permissions,
}}
/>
);
}
Usage Limits per User
function LimitedChat() {
const { user, token } = useAuth();
const [messages, setMessages] = useState<Message[]>([]);
const [usage, setUsage] = useState({ count: 0, limit: 100 });
// Check usage before allowing chat
useEffect(() => {
async function checkUsage() {
const response = await fetch(`/api/users/${user.id}/usage`);
const data = await response.json();
setUsage(data);
}
if (user) checkUsage();
}, [user?.id]);
if (usage.count >= usage.limit) {
return <div>You've reached your monthly limit. Upgrade to continue.</div>;
}
return (
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={(newMessages) => {
setMessages(newMessages);
// Update usage count based on new messages
setUsage(prev => ({
...prev,
count: prev.count + 1,
}));
}}
authToken={token}
context={{
userId: user?.id,
userName: user?.name,
}}
/>
);
}
Security Best Practices
1. Never Expose API Keys in Frontend
The InAppAI component uses your Agent ID (a public identifier) to connect to the backend. Your actual API keys for OpenAI, Anthropic, etc. are stored securely in the InAppAI backend and never exposed to the browser.
// Safe - Agent ID is a public identifier
<InAppAI agentId="your-agent-id" />
// Your AI provider API keys are configured in the InAppAI dashboard
// and never sent to the browser
2. Validate User Context on Backend
Always validate user identity on the backend:
// Backend: Don't trust client context
app.post('/api/chat', authenticate, async (req, res) => {
const userId = req.user.id; // From verified JWT
// Don't use client-provided userId
// const userId = req.body.context.userId;
const conversation = await getConversation(userId);
// ...
});
3. Use HTTPS
Always use HTTPS in production to protect user data and tokens.
Anonymous vs Authenticated
InAppAI supports both anonymous and authenticated users, with important differences in how rate limiting works.
Rate Limiting Differences
| Aspect | Anonymous Users | Authenticated Users |
|---|---|---|
| Identifier | IP address | User ID from JWT (authToken) |
| Rate Limit Scope | Per IP address | Per individual user |
| Shared Limits | All users from same IP share limits | Each user has individual limits |
| Per-Minute Limit | Default: 60 requests/minute | Default: 60 requests/minute |
| Monthly Limit | Based on subscription plan | Based on subscription plan |
How It Works
Anonymous users (no authToken) are identified by IP address:
- Multiple users behind the same IP (e.g., corporate network, NAT) share the same rate limit counter
- If one user sends 60 requests/minute, all users from that IP are blocked
Authenticated users (with authToken) are identified by their JWT user ID:
- Each user gets their own rate limit counter
- Users don’t affect each other’s limits
- Better experience for high-traffic applications
Rate Limit Responses
When rate limits are exceeded:
Per-Minute Limit (HTTP 429):
{
"error": "Too Many Requests",
"message": "Rate limit exceeded. Maximum 60 requests per minute allowed.",
"retryAfter": 60
}
Monthly Plan Limit (HTTP 402):
{
"error": "Payment Required",
"message": "You have reached your billing period request limit...",
"limit": 50000,
"current": 50000,
"remaining": 0,
"resetDate": "2025-02-01T00:00:00.000Z"
}
Supporting Both Modes
function FlexibleChat() {
const [messages, setMessages] = useState<Message[]>([]);
const { user, token } = useAuth();
// Generate session ID for anonymous users
const [sessionId] = useState(() => `anon_${Date.now()}_${Math.random()}`);
return (
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={setMessages}
// Only provide authToken for authenticated users
authToken={user ? token : undefined}
// Different context for auth vs anonymous
context={user ? {
userId: user.id,
userName: user.name,
authenticated: true,
} : {
sessionId: sessionId,
authenticated: false,
}}
/>
);
}
Best Practice: Encourage Authentication
For better rate limiting and user experience, encourage users to authenticate:
function ChatWithAuthPrompt() {
const { user, token } = useAuth();
const [messages, setMessages] = useState<Message[]>([]);
return (
<div>
{!user && (
<div className="auth-banner">
<p>Sign in for personalized responses and better experience</p>
<button onClick={openLoginModal}>Sign In</button>
</div>
)}
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={setMessages}
authToken={user ? token : undefined}
context={user ? {
userId: user.id,
userName: user.name,
} : undefined}
/>
</div>
);
}
Next Steps
- Context - Send user-specific context
- Persistence - Save per-user conversations
- Security - Comprehensive security guide