Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

跨域认证

当你的 Agent 部署后,为了保证安全,从客户端发送一个 token,然后在服务器端验证它。本指南涵盖与 agent 的 WebSocket 连接的认证模式。

WebSocket 认证

WebSocket 不是 HTTP,因此在跨域连接时握手是受限的。

你不能:

  • 在升级期间发送自定义头
  • 在连接时发送 Authorization: Bearer ...

你可以:

  • 在连接 URL 中作为查询参数放置一个签名的、短期有效的 token
  • 在你的服务器连接路径中验证 token

注意

切勿在 URL 中放置原始秘密。使用快速过期、限定到用户或房间的 JWT 或签名 token。

同源

如果客户端和服务器共享源,浏览器在 WebSocket 握手期间会发送 cookie。基于会话的认证可以在这里工作。优先使用 HTTP-only cookie。

跨源

Cookie 在跨源时不起作用。在 URL 查询中传递凭据,然后在服务器上验证。

使用示例

静态认证

JavaScript


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.

}


Explain Code

TypeScript


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.

}


Explain Code

异步认证

在连接前构建查询值。使用 Suspense 处理异步设置。

JavaScript


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.

}


function App() {

  return (

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

      <ChatComponent />

    </Suspense>

  );

}


Explain Code

TypeScript


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.

}


function App() {

  return (

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

      <ChatComponent />

    </Suspense>

  );

}


Explain Code

JWT 刷新模式

当连接因认证错误而失败时刷新 token。

JavaScript


import { useAgent } from "agents/react";

import { useCallback } from "react";


const validateToken = async (token) => {

  // 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 = async () => {

  // 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) {

  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

  });


  return agent;

}


Explain Code

TypeScript


import { useAgent } from "agents/react";

import { useCallback } 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 = async () => {

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

  });


  return agent;

}


Explain Code

跨域认证

在连接到另一个主机时在 URL 中传递凭据,然后在服务器上验证。

静态跨域认证

JavaScript


import { useAgent } from "agents/react";


function StaticCrossDomainAuth() {

  const agent = useAgent({

    agent: "my-agent",

    host: "https://my-agent.example.workers.dev",

    query: {

      token: "demo-token-123",

      userId: "demo-user",

    },

  });


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

}


Explain Code

TypeScript


import { useAgent } from "agents/react";


function StaticCrossDomainAuth() {

  const agent = useAgent({

    agent: "my-agent",

    host: "https://my-agent.example.workers.dev",

    query: {

      token: "demo-token-123",

      userId: "demo-user",

    },

  });


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

}


Explain Code

异步跨域认证

JavaScript


import { useAgent } from "agents/react";

import { useCallback } from "react";


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: "https://my-agent.example.workers.dev",

    query: asyncQuery,

  });


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

}


Explain Code

TypeScript


import { useAgent } from "agents/react";

import { useCallback } from "react";


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: "https://my-agent.example.workers.dev",

    query: asyncQuery,

  });


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

}


Explain Code

服务器端验证

在服务器端,在 onConnect handler 中验证 token:

JavaScript


import { Agent, Connection, ConnectionContext } from "agents";


export class SecureAgent extends Agent {

  async onConnect(connection, ctx) {

    const url = new URL(ctx.request.url);

    const token = url.searchParams.get("token");

    const userId = url.searchParams.get("userId");


    // Verify the token

    if (!token || !(await this.verifyToken(token, userId))) {

      connection.close(4001, "Unauthorized");

      return;

    }


    // Store user info on the connection state

    connection.setState({ userId, authenticated: true });

  }


  async verifyToken(token, userId) {

    // Implement your token verification logic

    // For example, verify a JWT signature, check expiration, etc.

    try {

      const payload = await verifyJWT(token, this.env.JWT_SECRET);

      return payload.sub === userId && payload.exp > Date.now() / 1000;

    } catch {

      return false;

    }

  }


  async onMessage(connection, message) {

    // Check if connection is authenticated

    if (!connection.state?.authenticated) {

      connection.send(JSON.stringify({ error: "Not authenticated" }));

      return;

    }


    // Process message for authenticated user

    const userId = connection.state.userId;

    // ...

  }

}


Explain Code

TypeScript


import { Agent, Connection, ConnectionContext } from "agents";


export class SecureAgent extends Agent {

  async onConnect(connection: Connection, ctx: ConnectionContext) {

    const url = new URL(ctx.request.url);

    const token = url.searchParams.get("token");

    const userId = url.searchParams.get("userId");


    // Verify the token

    if (!token || !(await this.verifyToken(token, userId))) {

      connection.close(4001, "Unauthorized");

      return;

    }


    // Store user info on the connection state

    connection.setState({ userId, authenticated: true });

  }


  private async verifyToken(token: string, userId: string): Promise<boolean> {

    // Implement your token verification logic

    // For example, verify a JWT signature, check expiration, etc.

    try {

      const payload = await verifyJWT(token, this.env.JWT_SECRET);

      return payload.sub === userId && payload.exp > Date.now() / 1000;

    } catch {

      return false;

    }

  }


  async onMessage(connection: Connection, message: string) {

    // Check if connection is authenticated

    if (!connection.state?.authenticated) {

      connection.send(JSON.stringify({ error: "Not authenticated" }));

      return;

    }


    // Process message for authenticated user

    const userId = connection.state.userId;

    // ...

  }

}


Explain Code

最佳实践

  1. 使用短期 token - URL 中的 token 可能被记录。保持过期时间短(分钟,而不是小时)。
  2. 适当限定 token 范围 - 在 token 声明中包含 agent 名称或实例,以防止 token 在 agent 之间被重用。
  3. 每次连接都验证 - 始终在 onConnect 中验证 token,而不仅仅是一次。
  4. 使用 HTTPS - 在生产中始终使用安全的 WebSocket 连接(wss://)。
  5. 轮换 secret - 定期轮换你的 JWT 签名密钥或 token secret。
  6. 记录认证失败 - 跟踪失败的认证尝试以进行安全监控。

下一步

Routing 路由和认证 hook。

WebSockets 实时双向通信。

Agents API Agents SDK 的完整 API 参考。