branch:
README.md
5115 bytesRaw
# Auth Agent

Demonstrates how to protect agent WebSocket and HTTP connections with **JWT authentication**.

> **This is a demo, not a production auth system.** The `/api/token` endpoint
> hands out JWTs to anyone — it simulates your existing auth service. In
> production, replace it with your own identity provider. **The patterns to
> copy are `onBeforeConnect` and `onBeforeRequest`** — those stay the same
> regardless of how you issue tokens.

## What it shows

- Protecting WebSocket connections via `onBeforeConnect`
- Protecting HTTP agent routes via `onBeforeRequest`
- Issuing JWTs with [jose](https://github.com/panva/jose) (HMAC-SHA256)
- Passing user identity from JWT claims into the agent

## Getting started

```sh
npm install

# Create .env with your secret
echo "AUTH_SECRET=$(openssl rand -base64 32)" > .env

# Start dev server
npm start
```

## The pattern you should copy

**Use `onBeforeConnect` to protect WebSocket connections and `onBeforeRequest`
to protect HTTP requests.** Everything else in this example (the token endpoint,
the login form, localStorage) is scaffolding you will replace with your own auth.

### Protect WebSocket connections

WebSocket upgrade requests do not support custom headers. Pass the JWT as a
query parameter and verify it in `onBeforeConnect`:

```typescript
routeAgentRequest(request, env, {
  onBeforeConnect: async (req) => {
    const token = new URL(req.url).searchParams.get("token");
    if (!token)
      return Response.json({ error: "Missing token" }, { status: 401 });

    const payload = await verifyToken(env, token);
    if (!payload)
      return Response.json({ error: "Unauthorized" }, { status: 401 });

    // Return the original request to allow the connection
    return req;
  },
```

On the client, pass the token via the `query` option on `useAgent`:

```typescript
const agent = useAgent({
  agent: "ChatAgent",
  name: userName,
  query: async () => ({ token: getToken() || "" })
});
```

### Protect HTTP requests

The SDK also makes HTTP requests (e.g. fetching initial messages). These use
the same `query` option, so check both the `Authorization` header and the
query parameter:

```typescript
  onBeforeRequest: async (req) => {
    const authHeader = req.headers.get("Authorization");
    const token = authHeader?.startsWith("Bearer ")
      ? authHeader.slice(7)
      : new URL(req.url).searchParams.get("token");
    if (!token)
      return Response.json({ error: "Missing token" }, { status: 401 });

    const payload = await verifyToken(env, token);
    if (!payload)
      return Response.json({ error: "Unauthorized" }, { status: 401 });
    return req;
  }
});
```

## How the demo works end-to-end

```
Browser                          Worker                        Durable Object
──────                          ──────                        ──────────────
1. POST /api/token          ──► issueToken(name)              ← REPLACE THIS
   { "name": "Alice" }           with your own auth service
                                  ◄──── { token: "eyJ..." }

2. WebSocket /agents/*       ──► onBeforeConnect:             ← COPY THIS
   ?token=<jwt>                   jwtVerify(token, secret)
                                  ◄──── 401 or upgrade

3. HTTP /agents/*            ──► onBeforeRequest:             ← COPY THIS
   Bearer header or ?token=       jwtVerify(token, secret)
                                  ◄──── 401 or response
```

### Personalised responses

The Durable Object name is set to the user's name from the JWT `sub` claim.
The system prompt includes this name so the LLM can address the user personally:

```typescript
const userName = this.name; // DO name = user name from JWT
const result = streamText({
  system: `You are a helpful assistant. The user's name is ${userName}.`,
  ...
});
```

## File overview

| File                 | Purpose                                                        |
| -------------------- | -------------------------------------------------------------- |
| `src/server.ts`      | Worker entry — **token endpoint (replace), JWT verify (copy)** |
| `src/auth-client.ts` | Client-side token fetch and storage (replace with your auth)   |
| `src/client.tsx`     | React UI — name form + chat                                    |
| `src/index.tsx`      | React root with ThemeProvider                                  |
| `src/styles.css`     | Tailwind + Kumo + agents-ui imports                            |

## Environment variables

| Variable      | Required | Description                                            |
| ------------- | -------- | ------------------------------------------------------ |
| `AUTH_SECRET` | Yes      | HMAC secret for signing/verifying JWTs. Put in `.env`. |

## Deploying

1. Set the secret: `wrangler secret put AUTH_SECRET`
2. Deploy: `npm run deploy`

## Related examples

- [ai-chat](../ai-chat/) — chat agent without auth
- [playground](../playground/) — kitchen-sink showcase of all SDK features