***

title: Authentication
subtitle: Secure token exchange and session management
slug: authentication
---------------------

For clean Markdown of any page, append .md to the page URL. For a complete documentation index, see https://docs.sunnyhealthai.com/ask-sunny/llms.txt. For full documentation content, see https://docs.sunnyhealthai.com/ask-sunny/llms-full.txt.

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:

```typescript
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:

```typescript
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`](/api-reference/configuration/get-sdk-config) endpoint *before* any WebSocket connection is established:

```mermaid
sequenceDiagram
    participant App
    participant SDK
    participant ConfigAPI as /sdk/config
    participant WebSocket

    App->>SDK: createSunnyChat with partnerIdentifier, publicKey, authType
    SDK-->>App: Returns VanillaChatInstance immediately

    SDK->>ConfigAPI: GET /sdk/config (partner + API key headers)
    ConfigAPI-->>SDK: SdkAuthConfig (clientId, audience, token exchange URL)

    alt Token Exchange (proactive auth)
        SDK->>WebSocket: Connect in background
        WebSocket-->>SDK: session.started + sdk.session.created
        SDK->>App: Call idTokenProvider
        App-->>SDK: ID token
        SDK->>WebSocket: auth.upgrade with access token
        WebSocket-->>SDK: auth.upgraded (send button spinner → send arrow)
    else Passwordless
        Note over SDK: User chats anonymously; verification UI appears when AI prompts
        Note over SDK: WebSocket connects when user sends first message
    end
```

### 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

```typescript
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:

```typescript
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

```bash
npm install @auth0/auth0-react
```

```tsx
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

```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

```bash
npm install firebase
```

```tsx
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

```typescript
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:**

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

**Firebase:**

```typescript
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.

```typescript
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](/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](/profile-sync)** - Pre-populate user data during authentication
* **[Key Concepts](/concepts)** - Understand events, state management, and MCP approvals
* **[Artifacts](/artifacts)** - Parse and render rich content
* **[Overview](/overview)** - Understand SDK architecture