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:

1import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
2
3const chat = createSunnyChat({
4 container: document.getElementById("chat"),
5 partnerIdentifier: "acme-health",
6 publicKey: "pk-sunnyagents_abc_xyz",
7 authType: "tokenExchange",
8 idTokenProvider: async () => {
9 return localStorage.getItem("id_token");
10 },
11});

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:

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

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:

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

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

Runtime auth type switching

You can switch the authentication type at runtime:

1const chat = createSunnyChat({
2 container: document.getElementById("chat"),
3 partnerIdentifier: "acme-health",
4 publicKey: "pk-sunnyagents_abc_xyz",
5 authType: "passwordless",
6});
7
8// Later, switch to token exchange with a custom provider
9await chat.setAuthType("tokenExchange", {
10 idTokenProvider: async () => getMyToken(),
11});

Authentication providers

Auth0 with React

$npm install @auth0/auth0-react
1import { useAuth0 } from "@auth0/auth0-react";
2import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
3import { useEffect, useRef } from "react";
4import type { VanillaChatInstance } from "@sunnyhealthai/agents-sdk";
5
6export function AuthenticatedChat() {
7 const { getIdTokenClaims, isAuthenticated } = useAuth0();
8 const containerRef = useRef<HTMLDivElement>(null);
9 const chatRef = useRef<VanillaChatInstance | null>(null);
10
11 useEffect(() => {
12 if (!isAuthenticated || !containerRef.current) return;
13
14 chatRef.current = createSunnyChat({
15 container: containerRef.current!,
16 partnerIdentifier: "acme-health",
17 publicKey: "pk-sunnyagents_abc_xyz",
18 authType: "tokenExchange",
19 idTokenProvider: async () => {
20 const claims = await getIdTokenClaims();
21 return claims?.__raw || null;
22 },
23 });
24
25 return () => {
26 chatRef.current?.destroy();
27 };
28 }, [isAuthenticated, getIdTokenClaims]);
29
30 if (!isAuthenticated) {
31 return <div>Please log in to use chat</div>;
32 }
33
34 return <div ref={containerRef} />;
35}

Auth0 with Vanilla JavaScript

1import { createAuth0Client } from "@auth0/auth0-spa-js";
2import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
3
4const auth0 = await createAuth0Client({
5 domain: "your-domain.auth0.com",
6 clientId: "your-client-id",
7});
8
9const isAuthenticated = await auth0.isAuthenticated();
10
11if (!isAuthenticated) {
12 await auth0.loginWithRedirect();
13}
14
15const chat = createSunnyChat({
16 container: document.getElementById("chat"),
17 partnerIdentifier: "acme-health",
18 publicKey: "pk-sunnyagents_abc_xyz",
19 authType: "tokenExchange",
20 idTokenProvider: async () => {
21 const claims = await auth0.getIdTokenClaims();
22 return claims?.__raw || null;
23 },
24});

Firebase with React

$npm install firebase
1import { getAuth } from "firebase/auth";
2import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
3import { useEffect, useRef } from "react";
4import { useAuthState } from "react-firebase-hooks/auth";
5import type { VanillaChatInstance } from "@sunnyhealthai/agents-sdk";
6
7const auth = getAuth();
8
9export function FirebaseChat() {
10 const [user, loading] = useAuthState(auth);
11 const containerRef = useRef<HTMLDivElement>(null);
12 const chatRef = useRef<VanillaChatInstance | null>(null);
13
14 useEffect(() => {
15 if (!user || loading || !containerRef.current) return;
16
17 chatRef.current = createSunnyChat({
18 container: containerRef.current!,
19 partnerIdentifier: "acme-health",
20 publicKey: "pk-sunnyagents_abc_xyz",
21 authType: "tokenExchange",
22 idTokenProvider: async () => {
23 if (!user) return null;
24 return await user.getIdToken();
25 },
26 });
27
28 return () => {
29 chatRef.current?.destroy();
30 };
31 }, [user, loading]);
32
33 if (loading) return <div>Loading...</div>;
34 if (!user) return <div>Please log in</div>;
35
36 return <div ref={containerRef} />;
37}

Custom provider

1import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
2
3const chat = createSunnyChat({
4 container: document.getElementById("chat"),
5 partnerIdentifier: "acme-health",
6 publicKey: "pk-sunnyagents_abc_xyz",
7 authType: "tokenExchange",
8 idTokenProvider: async () => {
9 try {
10 const response = await fetch("/api/auth/token", {
11 credentials: "include",
12 });
13 if (!response.ok) return null;
14 const data = await response.json();
15 return data.idToken;
16 } catch (error) {
17 console.error("Failed to get ID token:", error);
18 return null;
19 }
20 },
21});

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:

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

Firebase:

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

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.

1const chat = createSunnyChat({
2 container: document.getElementById("chat"),
3 partnerIdentifier: "acme-health",
4 publicKey: "pk-sunnyagents_abc_xyz",
5 authType: "tokenExchange",
6 idTokenProvider: async () => getMyToken(),
7 authUpgradeProfileSync: {
8 user_profile: { first_name: "Jane", last_name: "Doe" },
9 user_address: {
10 address_line_1: "123 Main St",
11 city: "San Francisco",
12 state: "CA",
13 zip_code: "94102",
14 },
15 },
16});

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