← Создание агентов на Cloudflare / agents / runtime / operations
Аутентификация между доменами
После развёртывания агентов для безопасности отправляйте токен с клиента и проверяйте его на сервере. В этом руководстве описаны шаблоны аутентификации для WebSocket-соединений с агентами.
Аутентификация WebSocket
WebSockets не являются HTTP, поэтому handshake ограничен при установлении междоменных соединений.
Вы не можете отправлять:
- Пользовательские заголовки во время апгрейда
Authorization: Bearer ...при подключении
Вы можете:
- Размещение подписанного недолговечного токена в URL подключения в виде параметров запроса
- Проверьте токен на пути подключения вашего сервера
Тот же источник
Если клиент и сервер имеют общий origin, браузер будет отправлять cookies во время handshake WebSocket. Здесь может работать аутентификация на основе сессии. Отдавайте предпочтение cookies с флагом HTTP-only.
Между источниками
Поведение файлов cookie между источниками зависит от домена cookie и SameSite атрибутов, от того, являются ли два источника same-site, и от политики браузера в отношении сторонних cookie. Если полагаться на cookie нельзя, передайте краткоживущие учётные данные в параметре URL-запроса и проверьте их на сервере.
Примеры использования
Статическая аутентификация
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.
}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.
}Асинхронная аутентификация
Формируйте значения запроса непосредственно перед подключением. Используйте Suspense для асинхронной настройки.
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>
);
}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>
);
}Схема обновления JWT
useAgent разрешает асинхронный запрос перед подключением и повторно вычисляет его при переподключении. Каждый раз возвращайте новый короткоживущий токен приложения:
import { useAgent } from "agents/react";
import { useCallback } from "react";
function useJWTAgent(agentName) {
const asyncQuery = useCallback(async () => {
return { token: await getShortLivedAccessToken() };
}, []);
return useAgent({
agent: agentName,
query: asyncQuery,
});
}import { useAgent } from "agents/react";
import { useCallback } from "react";
declare function getShortLivedAccessToken(): Promise<string>;
function useJWTAgent(agentName: string) {
const asyncQuery = useCallback(async () => {
return { token: await getShortLivedAccessToken() };
}, []);
return useAgent({
agent: agentName,
query: asyncQuery,
});
}Аутентификация между доменами
Передавайте учётные данные в URL при подключении к другому хосту, а затем проверяйте их на сервере.
Статическая междоменная аутентификация
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.
}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.
}Асинхронная междоменная аутентификация
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.
}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.
}Проверка на стороне сервера
На стороне сервера проверьте токен в onConnect обработчик:
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;
// ...
}
}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;
// ...
}
}Рекомендации
-
Используйте недолговечные токены - Токены в URL могут попадать в логи. Устанавливайте короткий срок действия (минуты, а не часы).
-
Ограничивайте область действия токенов надлежащим образом - Указывайте имя или экземпляр агента в claims токена, чтобы токен нельзя было повторно использовать в других агентах.
-
Валидируйте при каждом подключении - всегда проверяйте токены в
onConnect, а не только один раз. -
Используйте HTTPS - всегда используйте защищённые соединения WebSocket (
wss://) в продакшене. -
Ротация секретов - Регулярно меняйте ключи подписи JWT или секреты токенов.
-
Регистрируйте неудачные попытки аутентификации - Отслеживайте неудачные попытки аутентификации для мониторинга безопасности.