Communication & Networking
Access Control
Authorize Rivet Actor actions, queue publishes, and event subscriptions with hooks that enforce access rules for each client connection.
Use access control to decide what authenticated clients are allowed to do.
This is authorization, not authentication:
- Use 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:
onBeforeConnectrejects unauthenticated or malformed connections.- Action handlers (
actions.*) enforce action permissions. queues.<name>.canPublishallows or denies inbound queue publishes.events.<name>.canSubscribeallows or denies event subscriptions.
Fail By Default
Use deny-by-default rules everywhere:
- Keep
onBeforeConnectstrict and reject invalid credentials. - In each action, explicitly allow expected roles and throw
forbiddenotherwise. - In
canPublishandcanSubscribe, returntrueonly for allowed roles and end withreturn false.
import { actor, event, queue, UserError } from "rivetkit";
type ConnParams = {
authToken: string;
};
type ConnState = {
userId: string;
role: "member" | "admin";
};
async function authenticate(
authToken: string,
): Promise<ConnState | null> {
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<ConnState> => {
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: allowfalse: deny withforbidden
Returning undefined, null, or any non-boolean throws an internal error.
Notes
canPublishonly applies to queue names defined inqueues.- Incoming queue messages for undefined queues are ignored and the publish succeeds as completed.
canSubscribeonly applies to event names defined inevents.- Broadcasting an event not defined in
eventsstill publishes to subscribers.