INTEGRITY Документация

Аутентификация между доменами

После развёртывания агентов для безопасности отправляйте токен с клиента и проверяйте его на сервере. В этом руководстве описаны шаблоны аутентификации для WebSocket-соединений с агентами.

Аутентификация WebSocket

WebSockets не являются HTTP, поэтому handshake ограничен при установлении междоменных соединений.

Вы не можете отправлять:

Вы можете:

Тот же источник

Если клиент и сервер имеют общий 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;
		// ...
	}
}

Рекомендации

  1. Используйте недолговечные токены - Токены в URL могут попадать в логи. Устанавливайте короткий срок действия (минуты, а не часы).

  2. Ограничивайте область действия токенов надлежащим образом - Указывайте имя или экземпляр агента в claims токена, чтобы токен нельзя было повторно использовать в других агентах.

  3. Валидируйте при каждом подключении - всегда проверяйте токены в onConnect, а не только один раз.

  4. Используйте HTTPS - всегда используйте защищённые соединения WebSocket (wss://) в продакшене.

  5. Ротация секретов - Регулярно меняйте ключи подписи JWT или секреты токенов.

  6. Регистрируйте неудачные попытки аутентификации - Отслеживайте неудачные попытки аутентификации для мониторинга безопасности.

Следующие шаги

Маршрутизация

Хуки маршрутизации и аутентификации.

WebSockets

Двусторонняя связь в реальном времени.

Пример агента GitHub OAuth

Защитите приложение, созданное с Agents, с помощью GitHub OAuth, HTTP-only куки и маршрутизации Durable Object на стороне сервера.

Agents API

Полный справочник API для Agents SDK.