Skip to navigation

Authentication

Secure token exchange and session management
View as Markdown

The Sunny Agents SDK supports two authentication modes: custom token exchange and passwordless authentication. With createSunnyChat, all authentication configuration is server-driven — you only specify the auth type and the server provides the necessary details automatically.

Authentication modes

Custom Token Exchange

For applications with custom authentication providers:

import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
const chat = createSunnyChat({
container: document.getElementById("chat"),
partnerIdentifier: "acme-health",
publicKey: "pk-sunnyagents_abc_xyz",
authType: "tokenExchange",
idTokenProvider: async () => {
return localStorage.getItem("id_token");
},
});

Features:

  • Server-persisted conversations
  • Access token authentication
  • Automatic token refresh
  • User-specific data

Passwordless Authentication

Email or SMS-based authentication without passwords. The SDK renders a verification UI directly in the chat:

import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
const chat = createSunnyChat({
container: document.getElementById("chat"),
partnerIdentifier: "acme-health",
publicKey: "pk-sunnyagents_abc_xyz",
authType: "passwordless",
});

Features:

  • Email or SMS authentication
  • No passwords required
  • WebSocket-based (no page refreshes)
  • Optional chat history migration
  • Automatic token management
  • In-chat verification UI

Authentication flow

When using createSunnyChat, auth configuration is fetched via HTTP from the /sdk/config endpoint before any WebSocket connection is established:

alt [Token Exchange (proactive auth)] [Passwordless] createSunnyChat with partnerIdentifier, publicKey, authType Returns VanillaChatInstance immediately GET /sdk/config (partner + API key headers) SdkAuthConfig (clientId, audience, token exchange URL) Connect in background session.started + sdk.session.created Call idTokenProvider ID token auth.upgrade with access token auth.upgraded (send button spinner → send arrow) User chats anonymously; verification UI appears when AI prompts WebSocket connects when user sends first message App SDK /sdk/config WebSocket

Step-by-step

Token Exchange:

  1. Create chat: Call createSunnyChat with authType: "tokenExchange" — returns immediately
  2. Fetch config: SDK calls GET /sdk/config to retrieve token exchange settings (background)
  3. Proactive connect: SDK connects WebSocket and establishes SDK session in the background
  4. Provide ID token: SDK calls your idTokenProvider to get the ID token
  5. Exchange token: SDK exchanges ID token for access token
  6. Upgrade auth: SDK sends auth.upgrade message with access token
  7. Authenticated: SDK receives auth.upgraded response — send button spinner becomes send arrow
  8. First message: User sends a message — no waiting, auth is already complete
  9. Refresh: SDK automatically refreshes token when expired

Configuration options

interface UnifiedSunnyChatOptions {
container: HTMLElement; // Required: DOM element for the chat UI
partnerIdentifier: string; // Required: Your partner name
publicKey: string; // Required: API key (pk-sunnyagents_...)
authType: SdkAuthType; // Required: "passwordless" | "tokenExchange"
idTokenProvider?: () => Promise<string | null>; // Required for tokenExchange
authUpgradeProfileSync?: AuthUpgradeProfileSyncData | (() => Promise<...>); // Pre-populate user data
websocketUrl?: string; // Override WebSocket URL (default: wss://chat.api.sunnyhealthai-staging.com)
headerTitle?: string; // Chat header title (default: "Sunny Agents")
placeholder?: string; // Input placeholder (default: "Ask anything...")
colors?: VanillaChatColors; // Theme colors
fontSize?: string; // Base font size (default: "14px")
fontFamily?: string; // Font family (default: "Lato")
dimensions?: VanillaChatDimensions; // Widget dimensions (see below)
devRoute?: string; // Developer route for token exchange
}
interface VanillaChatDimensions {
width?: string; // Modal width when expanded (default: "1390px")
height?: string; // Modal height when expanded (default: "980px")
triggerMaxWidth?: string; // Max width of collapsed trigger bar (default: "600px")
}

Runtime auth type switching

You can switch the authentication type at runtime:

const chat = createSunnyChat({
container: document.getElementById("chat"),
partnerIdentifier: "acme-health",
publicKey: "pk-sunnyagents_abc_xyz",
authType: "passwordless",
});
// Later, switch to token exchange with a custom provider
await chat.setAuthType("tokenExchange", {
idTokenProvider: async () => getMyToken(),
});

Authentication providers

Auth0 with React

npm install @auth0/auth0-react
import { useAuth0 } from "@auth0/auth0-react";
import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
import { useEffect, useRef } from "react";
import type { VanillaChatInstance } from "@sunnyhealthai/agents-sdk";
export function AuthenticatedChat() {
const { getIdTokenClaims, isAuthenticated } = useAuth0();
const containerRef = useRef<HTMLDivElement>(null);
const chatRef = useRef<VanillaChatInstance | null>(null);
useEffect(() => {
if (!isAuthenticated || !containerRef.current) return;
chatRef.current = createSunnyChat({
container: containerRef.current!,
partnerIdentifier: "acme-health",
publicKey: "pk-sunnyagents_abc_xyz",
authType: "tokenExchange",
idTokenProvider: async () => {
const claims = await getIdTokenClaims();
return claims?.__raw || null;
},
});
return () => {
chatRef.current?.destroy();
};
}, [isAuthenticated, getIdTokenClaims]);
if (!isAuthenticated) {
return <div>Please log in to use chat</div>;
}
return <div ref={containerRef} />;
}

Auth0 with Vanilla JavaScript

import { createAuth0Client } from "@auth0/auth0-spa-js";
import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
const auth0 = await createAuth0Client({
domain: "your-domain.auth0.com",
clientId: "your-client-id",
});
const isAuthenticated = await auth0.isAuthenticated();
if (!isAuthenticated) {
await auth0.loginWithRedirect();
}
const chat = createSunnyChat({
container: document.getElementById("chat"),
partnerIdentifier: "acme-health",
publicKey: "pk-sunnyagents_abc_xyz",
authType: "tokenExchange",
idTokenProvider: async () => {
const claims = await auth0.getIdTokenClaims();
return claims?.__raw || null;
},
});

Firebase with React

npm install firebase
import { getAuth } from "firebase/auth";
import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
import { useEffect, useRef } from "react";
import { useAuthState } from "react-firebase-hooks/auth";
import type { VanillaChatInstance } from "@sunnyhealthai/agents-sdk";
const auth = getAuth();
export function FirebaseChat() {
const [user, loading] = useAuthState(auth);
const containerRef = useRef<HTMLDivElement>(null);
const chatRef = useRef<VanillaChatInstance | null>(null);
useEffect(() => {
if (!user || loading || !containerRef.current) return;
chatRef.current = createSunnyChat({
container: containerRef.current!,
partnerIdentifier: "acme-health",
publicKey: "pk-sunnyagents_abc_xyz",
authType: "tokenExchange",
idTokenProvider: async () => {
if (!user) return null;
return await user.getIdToken();
},
});
return () => {
chatRef.current?.destroy();
};
}, [user, loading]);
if (loading) return <div>Loading...</div>;
if (!user) return <div>Please log in</div>;
return <div ref={containerRef} />;
}

Custom provider

import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
const chat = createSunnyChat({
container: document.getElementById("chat"),
partnerIdentifier: "acme-health",
publicKey: "pk-sunnyagents_abc_xyz",
authType: "tokenExchange",
idTokenProvider: async () => {
try {
const response = await fetch("/api/auth/token", {
credentials: "include",
});
if (!response.ok) return null;
const data = await response.json();
return data.idToken;
} catch (error) {
console.error("Failed to get ID token:", error);
return null;
}
},
});

Token refresh

The SDK automatically refreshes tokens before they expire using a check-on-send strategy: before every outgoing message, the SDK checks the access token’s JWT exp claim and, if the token is within 5 minutes of expiry, requests a fresh ID token from your idTokenProvider, exchanges it for a new access token, and sends an auth.refresh message to the server — all before the user’s message is sent. No timers or background polling are involved; refresh only happens when the user is actively sending messages.

No action is needed from your app. If your idTokenProvider needs to force-refresh, you can do so per provider:

Auth0:

idTokenProvider: async () => {
const claims = await getIdTokenClaims({ cache: false });
return claims?.__raw || null;
}

Firebase:

idTokenProvider: async () => {
if (!user) return null;
return await user.getIdToken(true);
}

Profile sync

You can pre-populate user data (profile, address, insurance, and dependents) during authentication by passing authUpgradeProfileSync to createSunnyChat. This data is sent alongside the auth.upgrade message, so the user doesn’t need to re-enter information your application already has.

const chat = createSunnyChat({
container: document.getElementById("chat"),
partnerIdentifier: "acme-health",
publicKey: "pk-sunnyagents_abc_xyz",
authType: "tokenExchange",
idTokenProvider: async () => getMyToken(),
authUpgradeProfileSync: {
user_profile: { first_name: "Jane", last_name: "Doe" },
user_address: {
address_line_1: "123 Main St",
city: "San Francisco",
state: "CA",
zip_code: "94102",
},
},
});

See the Profile Sync guide for full details on all supported data types including insurance and dependents.

Best practices

  1. Secure storage: Store ID tokens securely (not in localStorage for sensitive apps)
  2. Error handling: Always handle authentication errors
  3. Token refresh: Let SDK handle token refresh automatically

Next steps

  • Profile Sync - Pre-populate user data during authentication
  • Key Concepts - Understand events, state management, and MCP approvals
  • Artifacts - Parse and render rich content
  • Overview - Understand SDK architecture