Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

クロスドメイン認証

最終更新 Markdown で表示Agent セットアップ

Agents をデプロイしたあとは、セキュリティのためクライアントからトークンを送り、サーバーで検証します。このガイドでは、エージェントへの WebSocket 接続の認証パターンを扱います。

WebSocket 認証

WebSocket は HTTP ではないため、クロスドメイン接続ではハンドシェイクに制限があります。

送れないもの:

  • アップグレード時のカスタムヘッダー
  • 接続時の Authorization: Bearer ...

できること:

  • 署名済みの短命トークンを、クエリパラメーターとして接続 URL に入れる
  • サーバーの接続パスでトークンを検証する

同一オリジン

クライアントとサーバーがオリジンを共有する場合、ブラウザーは WebSocket ハンドシェイク時に Cookie を送ります。ここではセッションベースの認証が使えます。HTTP-only Cookie を推奨します。

クロスオリジン

クロスオリジンの Cookie 動作は、Cookie のドメインと SameSite 属性、2 つのオリジンが 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. トークンのスコープを適切にする — トークンのクレームにエージェント名またはインスタンスを含め、エージェント間でのトークン再利用を防ぎます。

  3. 接続のたびに検証する — トークンは一度きりではなく、必ず onConnect で検証します。

  4. HTTPS を使う — 本番では常にセキュアな WebSocket 接続(wss://)を使います。

  5. シークレットをローテーションする — JWT の署名鍵やトークンシークレットは定期的にローテーションします。

  6. 認証失敗をログする — セキュリティ監視のため、認証失敗を追跡します。

次のステップ

WebSockets

リアルタイムの双方向通信です。

Agents API

Agents SDK の API リファレンス全体です。

役に立ちましたか?