branch:
cross-domain-authentication.md
4093 bytesRaw
# Cross-Domain Authentication

When your Agents are deployed, to keep things secure, send a token from the client, then verify it on the server. This mirrors the shape used in PartyKit’s auth guide.

## WebSocket authentication

WebSockets are not HTTP, so the handshake is limited when making cross-domain connections.

**What you cannot send**

- Custom headers during the upgrade
- `Authorization: Bearer ...` on connect

**What works**

- Put a signed, short-lived token in the connection URL as query parameters
- Verify the token in your server’s connect path

> Tip: never place raw secrets in URLs. Prefer a JWT or a signed token that expires quickly and is scoped to the user or room.

### Same origin

If the client and server share the origin, the browser will send cookies during the WebSocket handshake. Session based auth can work here. Prefer HTTP-only cookies.

### Cross origin

Cookies do not help across origins. Pass credentials in the URL query, then verify on the server.

## Usage examples

### Static authentication

```ts
import { useAgent } from "agents/react";

function ChatComponent() {
  const agent = useAgent({
    agent: "my-agent",
    query: {
      token: "demo-token-123",
      userId: "demo-user"
    }
  });

  // Use agent to make calls, access state, etc.
}
```

### Async authentication

Build query values right before connect. Use Suspense for async setup.

```ts
import { useAgent } from "agents/react";
import { Suspense, useCallback } from "react";

function ChatComponent() {
  const asyncQuery = useCallback(async () => {
    const [token, user] = await Promise.all([getAuthToken(), getCurrentUser()]);
    return {
      token,
      userId: user.id,
      timestamp: Date.now().toString()
    };
  }, []);

  const agent = useAgent({
    agent: "my-agent",
    query: asyncQuery
  });

  // Use agent to make calls, access state, etc.
}

<Suspense fallback={<div>Authenticating...</div>}>
  <ChatComponent />
</Suspense>
```

### JWT refresh pattern

Refresh the token when the connection fails due to authentication error.

```ts
import { useAgent } from "agents/react";
import { useCallback, useEffect } from "react";

const validateToken = async (token: string) => {
  // An example of how you might implement this
  const res = await fetch(`${API_HOST}/api/users/me`, {
    headers: {
      Authorization: `Bearer ${token}`
    }
  });

  return res.ok;
};

const refreshToken = () => {
  // Depends on implementation:
  // - You could use a longer-lived token to refresh the expired token
  // - De-auth the app and prompt the user to log in manually
  // - ...
};

function useJWTAgent(agentName: string) {
  const asyncQuery = useCallback(async () => {
    let token = localStorage.getItem("jwt");

    // If no token OR the token is no longer valid
    // request a fresh token
    if (!token && !(await validateToken(token))) {
      token = await refreshToken();
      localStorage.setItem("jwt", token);
    }

    return {
      token
    };
  }, []);

  const agent = useAgent({
    agent: agentName,
    query: asyncQuery,
    queryDeps: [] // Run on mount
  });
}
```

## Cross-domain authentication

Pass credentials in the URL when connecting to another host, then verify on the server.

```ts
import { useAgent } from "agents/react";
import { useCallback } from "react";

// Static cross-domain auth
function StaticCrossDomainAuth() {
  const agent = useAgent({
    agent: "my-agent",
    host: "http://localhost:8788",
    query: {
      token: "demo-token-123",
      userId: "demo-user"
    }
  });

  // Use agent to make calls, access state, etc.
}

// Async cross-domain auth
function AsyncCrossDomainAuth() {
  const asyncQuery = useCallback(async () => {
    const [token, user] = await Promise.all([getAuthToken(), getCurrentUser()]);
    return {
      token,
      userId: user.id,
      timestamp: Date.now().toString()
    };
  }, []);

  const agent = useAgent({
    agent: "my-agent",
    host: "http://localhost:8788",
    query: asyncQuery
  });

  // Use agent to make calls, access state, etc.
}
```