INTEGRITY Dokumentace

Autentizace mezi doménami

Aby byly vaše nasazené Agents zabezpečené, odešlete token z klienta a poté jej ověřte na serveru. Tento návod popisuje vzory ověřování pro připojení WebSocket k agentům.

Autentizace WebSocket

WebSockets nejsou HTTP, takže handshake je při vytváření mezidoménových připojení omezený.

Nemůžete odesílat:

Můžete:

Stejný původ

Pokud klient a server sdílejí stejný origin, prohlížeč odešle cookies už během handshaku WebSocket. Zde může fungovat i autentizace založená na relaci. Upřednostněte cookies typu HTTP-only.

Mezi zdroji

Chování souborů cookie mezi zdroji závisí na doméně cookie a SameSite atributech, na tom, zda mají obě origins stejný web (same-site), a na zásadách prohlížeče pro soubory cookie třetích stran. Pokud se nemůžete spolehnout na cookie, předejte krátkodobé pověření v dotazu URL a ověřte ho na serveru.

Příklady použití

Statická autentizace

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.
}

Asynchronní autentizace

Hodnoty dotazu sestavte těsně před připojením. Pro asynchronní nastavení použijte 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>
	);
}

Vzor obnovy JWT

useAgent před připojením vyřeší asynchronní dotaz a při každém opětovném připojení ho znovu vyhodnotí. Pokaždé vraťte nový, krátkodobý aplikační token:

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,
	});
}

Autentizace mezi doménami

Přihlašovací údaje předejte v URL adrese při připojování k jinému hostiteli a ověřte je na serveru.

Statická autentizace mezi doménami

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.
}

Asynchronní autentizace mezi doménami

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.
}

Ověření na straně serveru

Na straně serveru ověřte token v onConnect handler:

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;
		// ...
	}
}

Osvědčené postupy

  1. Použití krátkodobých tokenů - Tokeny v URL mohou být zaznamenány v logu. Udržujte krátkou dobu platnosti (minuty, ne hodiny).

  2. Vhodně omezte rozsah tokenů - Do deklarací tokenu zahrňte název nebo instanci agenta, abyste zabránili opětovnému použití tokenu mezi agenty.

  3. Ověřujte při každém připojení - Vždy ověřujte tokeny v onConnect, nikoli jen jednou.

  4. Použití HTTPS - Vždy používejte zabezpečená WebSocket připojení (wss://) v produkci.

  5. Rotace tajných klíčů - Pravidelně obměňujte podpisové klíče JWT nebo tajné klíče tokenů.

  6. Protokolujte neúspěšná ověření - Sledujte neúspěšné pokusy o ověření pro účely bezpečnostního monitoringu.

Další kroky

Směrování

Hooky pro směrování a ověřování.

WebSockets

Obousměrná komunikace v reálném čase.

Příklad agenta GitHub OAuth

Chraňte aplikaci postavenou na Agents pomocí GitHub OAuth, cookies HTTP-only a směrování Durable Object vlastněného serverem.

Agents API

Kompletní referenční dokumentace API pro Agents SDK.