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 null for 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

AspectAnonymous UsersAuthenticated Users
IdentifierIP addressUser ID from JWT (authToken)
Rate Limit ScopePer IP addressPer individual user
Shared LimitsAll users from same IP share limitsEach user has individual limits
Per-Minute LimitDefault: 60 requests/minuteDefault: 60 requests/minute
Monthly LimitBased on subscription planBased 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