# Access Control > Source: `src/content/docs/actors/access-control.mdx` > Canonical URL: https://rivet.dev/docs/actors/access-control > Description: Authorize actions, queue publishes, and event subscriptions with explicit hooks. --- Use access control to decide what authenticated clients are allowed to do. This is authorization, not authentication: - Use [authentication](/docs/actors/authentication) to identify who is calling. - Use access-control rules to decide what they can do after connecting. ## Permission Surfaces RivetKit authorization is explicit per surface: - `onBeforeConnect` rejects unauthenticated or malformed connections. - Action handlers (`actions.*`) enforce action permissions. - `queues..canPublish` allows or denies inbound queue publishes. - `events..canSubscribe` allows or denies event subscriptions. ## Fail By Default Use deny-by-default rules everywhere: 1. Keep `onBeforeConnect` strict and reject invalid credentials. 2. In each action, explicitly allow expected roles and throw `forbidden` otherwise. 3. In `canPublish` and `canSubscribe`, return `true` only for allowed roles and end with `return false`. ```ts import { actor, event, queue, UserError } from "rivetkit"; type ConnParams = { authToken: string; }; type ConnState = { userId: string; role: "member" | "admin"; }; async function authenticate( authToken: string, ): Promise { if (authToken === "admin-token") { return { userId: "admin-1", role: "admin" }; } if (authToken === "member-token") { return { userId: "member-1", role: "member" }; } return null; } export const chatRoom = actor({ state: { messages: [] as Array<{ userId: string; text: string }> }, onBeforeConnect: async (_c, params: ConnParams) => { if (!params.authToken) { throw new UserError("Forbidden", { code: "forbidden" }); } const session = await authenticate(params.authToken); if (!session) { throw new UserError("Forbidden", { code: "forbidden" }); } }, createConnState: async (_c, params: ConnParams): Promise => { const session = await authenticate(params.authToken); if (!session) { throw new UserError("Forbidden", { code: "forbidden" }); } return session; }, events: { messages: event<{ userId: string; text: string }>(), moderationLog: event<{ entry: string }>({ canSubscribe: (c) => { if (c.conn?.state.role === "admin") { return true; } return false; }, }), }, queues: { moderationJobs: queue<{ action: "ban"; userId: string }>({ canPublish: (c) => { if (c.conn?.state.role === "admin") { return true; } return false; }, }), }, actions: { sendMessage: (c, text: string) => { const role = c.conn?.state.role; const userId = c.conn?.state.userId; if (!userId || (role !== "member" && role !== "admin")) { throw new UserError("Forbidden", { code: "forbidden" }); } const message = { userId, text }; c.state.messages.push(message); c.broadcast("messages", message); }, }, }); ``` ## Return Value Contract `canPublish` and `canSubscribe` must return a boolean: - `true`: allow - `false`: deny with `forbidden` Returning `undefined`, `null`, or any non-boolean throws an internal error. ## Notes - `canPublish` only applies to queue names defined in `queues`. - Incoming queue messages for undefined queues are ignored and the publish succeeds as completed. - `canSubscribe` only applies to event names defined in `events`. - Broadcasting an event not defined in `events` still publishes to subscribers. _Source doc path: /docs/actors/access-control_