# Authentication > Source: `src/content/docs/actors/authentication.mdx` > Canonical URL: https://rivet.dev/docs/actors/authentication > Description: Secure your actors with authentication and authorization. --- ## Do You Need Authentication? ### Rivet Cloud Actors are private by default on Rivet Cloud. Only requests with the publishable token can interact with actors. - **Backend-only actors**: If your publishable token is only included in your backend, then authentication is not necessary. - **Frontend-accessible actors**: If your publishable token is included in your frontend, then implementing authentication is recommended. ### Self-Hosted Actors are public by default on self-hosted Rivet. Anyone can access them without a token. - **Only accessible within private network**: If Rivet is only accessible within your private network, then authentication is not necessary. - **Rivet exposed to the public internet**: If Rivet is configured to accept traffic from the public internet, then implementing authentication is recommended. ## Authentication Connections Authentication is configured through either: - `onBeforeConnect` for simple pass/fail validation - `createConnState` when you need to access user data in your actions via `c.conn.state` ## Access Control After a connection is authenticated, use [Access Control](/docs/actors/access-control) to enforce authorization: - Check permissions in action handlers. - Use `queues..canPublish` to gate inbound queue publishes. - Use `events..canSubscribe` to gate event subscriptions. ### `onBeforeConnect` The `onBeforeConnect` hook validates credentials before allowing a connection. Throw an error to reject the connection. ```typescript import { actor, UserError } from "rivetkit"; interface ConnParams { authToken: string; } // Example token validation function async function validateToken(token: string, roomKey: string[]): Promise { // In production, verify JWT or call auth service return token.length > 0 && roomKey.length > 0; } interface Message { text: string; timestamp: number; } const chatRoom = actor({ state: { messages: [] as Message[] }, onBeforeConnect: async (c, params: ConnParams) => { const roomName = c.key; const isValid = await validateToken(params.authToken, roomName); if (!isValid) { throw new UserError("Forbidden", { code: "forbidden" }); } }, actions: { sendMessage: (c, text: string) => { c.state.messages.push({ text, timestamp: Date.now() }); }, }, }); ``` ### `createConnState` Use `createConnState` to extract user data from credentials and store it in connection state. This data is accessible in actions via `c.conn.state`. Like `onBeforeConnect`, throwing an error will reject the connection. See [connections](/docs/actors/connections) for more details. ```typescript import { actor, UserError } from "rivetkit"; interface ConnParams { authToken: string; } interface ConnState { userId: string; role: string; } interface Message { userId: string; text: string; timestamp: number; } // Example token validation function async function validateToken(token: string, roomKey: string[]): Promise<{ sub: string; role: string } | null> { // In production, verify JWT or call auth service if (token.length > 0 && roomKey.length > 0) { return { sub: "user-123", role: "member" }; } return null; } const chatRoom = actor({ state: { messages: [] as Message[] }, createConnState: async (c, params: ConnParams): Promise => { const roomName = c.key; const payload = await validateToken(params.authToken, roomName); if (!payload) { throw new UserError("Forbidden", { code: "forbidden" }); } return { userId: payload.sub, role: payload.role, }; }, actions: { sendMessage: (c, text: string) => { // Access user data via c.conn.state const { userId, role } = c.conn.state; if (role !== "member") { throw new UserError("Insufficient permissions", { code: "insufficient_permissions" }); } c.state.messages.push({ userId, text, timestamp: Date.now() }); c.broadcast("newMessage", { userId, text }); }, }, }); ``` ## Available Auth Data Authentication hooks have access to several properties: | Property | Description | |----------|-------------| | `params` | Custom data passed by the client when connecting (see [connection params](/docs/actors/connections#extracting-data-from-connection-params)) | | `c.request` | The underlying HTTP request object | | `c.request.headers` | Request headers for tokens, API keys (does not work for `.connect()`) | | `c.state` | Actor state for authorization decisions (see [state](/docs/actors/state)) | | `c.key` | The actor's key (see [keys](/docs/actors/keys)) | It's recommended to use `params` instead of `c.request.headers` whenever possible since it works for both HTTP & WebSocket connections. ## Client Usage ### Passing Credentials Pass authentication data when connecting. Use `getParams` when you need a fresh JWT for every connection or reconnect: ```typescript Connection import { createClient } from "rivetkit/client"; async function getAuthToken(): Promise { return "jwt-token-here"; } const client = createClient(); const chat = client.chatRoom.getOrCreate(["general"], { getParams: async () => ({ authToken: await getAuthToken(), }), }); // Authentication will happen on connect by reading connection parameters const connection = chat.connect(); ``` ```typescript Stateless-Action import { createClient } from "rivetkit/client"; const client = createClient(); const chat = client.chatRoom.getOrCreate(["general"], { params: { authToken: "jwt-token-here" }, }); // Authentication will happen when calling the action by reading input // parameters await chat.sendMessage("Hello, world!"); ``` ```typescript HTTP-Headers import { createClient } from "rivetkit/client"; // This only works for stateless actions, not WebSockets const client = createClient({ headers: { Authorization: "Bearer my-token", }, }); const chat = client.chatRoom.getOrCreate(["general"]); // Authentication will happen when calling the action by reading headers await chat.sendMessage("Hello, world!"); ``` ### Handling Errors Authentication errors use the same system as regular errors. See [errors](/docs/actors/errors) for more details. ```typescript Connection import { actor, setup } from "rivetkit"; import { ActorError, createClient } from "rivetkit/client"; // Define actor with protected action const myActor = actor({ state: {}, actions: { protectedAction: (c) => ({ success: true }) } }); const registry = setup({ use: { myActor } }); const client = createClient("http://localhost:6420"); const actorHandle = await client.myActor.getOrCreate(); // Helper to show errors function showError(message: string) { console.error(message); } const conn = actorHandle.connect(); conn.onError((error: ActorError) => { if (error.code === "forbidden") { window.location.href = "/login"; } else if (error.code === "insufficient_permissions") { showError("You don't have permission for this action"); } }); ``` ```typescript Stateless-Action import { actor, setup } from "rivetkit"; import { ActorError, createClient } from "rivetkit/client"; // Define actor with protected action const myActor = actor({ state: {}, actions: { protectedAction: (c) => ({ success: true }) } }); const registry = setup({ use: { myActor } }); const client = createClient("http://localhost:6420"); const actorHandle = await client.myActor.getOrCreate(); // Helper to show errors function showError(message: string) { console.error(message); } try { const result = await actorHandle.protectedAction(); } catch (error) { if (error instanceof ActorError && error.code === "forbidden") { window.location.href = "/login"; } else if (error instanceof ActorError && error.code === "insufficient_permissions") { showError("You don't have permission for this action"); } } ``` ## Examples ### JWT Validate JSON Web Tokens and extract user claims: ```typescript import { actor, UserError } from "rivetkit"; interface ConnParams { token: string; } interface ConnState { userId: string; role: string; permissions: string[]; } interface JwtPayload { sub: string; role: string; permissions?: string[]; } // Example JWT verification function - in production use a JWT library function verifyJwt(token: string, secret: string): JwtPayload { // This is a simplified example - use jsonwebtoken or similar in production const parts = token.split("."); if (parts.length !== 3) throw new Error("Invalid token"); const payload = JSON.parse(atob(parts[1])) as JwtPayload; return payload; } const jwtActor = actor({ state: {}, createConnState: (c, params: ConnParams): ConnState => { try { const payload = verifyJwt(params.token, process.env.JWT_SECRET || "secret"); return { userId: payload.sub, role: payload.role, permissions: payload.permissions || [], }; } catch { throw new UserError("Invalid or expired token", { code: "invalid_token" }); } }, actions: { protectedAction: (c) => { if (!c.conn.state.permissions.includes("write")) { throw new UserError("Write permission required", { code: "forbidden" }); } return { success: true }; }, }, }); ``` ### External Auth Provider Validate credentials against an external authentication service: ```typescript import { actor, UserError } from "rivetkit"; interface ConnParams { apiKey: string; } interface ConnState { userId: string; tier: string; } const apiActor = actor({ state: {}, createConnState: async (c, params: ConnParams): Promise => { const response = await fetch(`https://api.my-auth-provider.com/validate`, { method: "POST", headers: { "X-API-Key": params.apiKey }, }); if (!response.ok) { throw new UserError("Invalid API key", { code: "invalid_api_key" }); } const data = await response.json(); return { userId: data.id, tier: data.tier }; }, actions: { premiumAction: (c) => { if (c.conn.state.tier !== "premium") { throw new UserError("Premium subscription required", { code: "forbidden" }); } return "Premium content"; }, }, }); ``` ### Using `c.state` In Authorization Access actor state via `c.state` and the actor's key via `c.key` to make authorization decisions: ```typescript import { actor, UserError } from "rivetkit"; interface ConnParams { userId?: string; } const userProfile = actor({ state: { ownerId: "user-123", isPrivate: true, }, onBeforeConnect: (c, params: ConnParams) => { // Use actor state to check access permissions if (c.state.isPrivate && params.userId !== c.state.ownerId) { throw new UserError("Access denied to private profile", { code: "forbidden" }); } }, actions: { getProfile: (c) => ({ ownerId: c.state.ownerId }), }, }); ``` ### Role-Based Access Control Create helper functions for common authorization patterns: ```typescript import { actor, UserError } from "rivetkit"; const ROLE_HIERARCHY = { user: 1, moderator: 2, admin: 3 }; interface ConnState { role: keyof typeof ROLE_HIERARCHY; permissions: string[]; } // Example token validation function async function validateToken(token: string): Promise<{ role: keyof typeof ROLE_HIERARCHY; permissions: string[] }> { // In production, verify JWT or call auth service return { role: "user", permissions: ["read", "edit_posts"] }; } function requireRole(requiredRole: keyof typeof ROLE_HIERARCHY) { return (c: { conn: { state: ConnState } }) => { const userRole = c.conn.state.role; if (ROLE_HIERARCHY[userRole] < ROLE_HIERARCHY[requiredRole]) { throw new UserError(`${requiredRole} role required`, { code: "forbidden" }); } }; } function requirePermission(permission: string) { return (c: { conn: { state: ConnState } }) => { if (!c.conn.state.permissions?.includes(permission)) { throw new UserError(`Permission '${permission}' required`, { code: "forbidden" }); } }; } const forumActor = actor({ state: {}, createConnState: async (c, params: { token: string }): Promise => { const user = await validateToken(params.token); return { role: user.role, permissions: user.permissions }; }, actions: { deletePost: (c, postId: string) => { requireRole("moderator")(c); // Delete post... }, editPost: (c, postId: string, content: string) => { requirePermission("edit_posts")(c); // Edit post... }, }, }); ``` ### Rate Limiting Use `c.vars` to track connection attempts and rate limit by user: ```typescript import { actor, UserError } from "rivetkit"; interface ConnParams { authToken: string; } interface RateLimitEntry { count: number; resetAt: number; } // Example token validation function async function validateToken(token: string): Promise<{ userId: string }> { // In production, verify JWT or call auth service return { userId: "user-123" }; } const rateLimitedActor = actor({ state: {}, createVars: () => ({ rateLimits: {} as Record }), onBeforeConnect: async (c, params: ConnParams) => { // Extract user ID const { userId } = await validateToken(params.authToken); // Check rate limit const now = Date.now(); const limit = c.vars.rateLimits[userId]; if (limit && limit.resetAt > now && limit.count >= 10) { throw new UserError("Too many requests, try again later", { code: "rate_limited" }); } // Update rate limit if (!limit || limit.resetAt <= now) { c.vars.rateLimits[userId] = { count: 1, resetAt: now + 60_000 }; } else { limit.count++; } }, actions: { getData: (c) => ({ success: true }), }, }); ``` The limits in this example are [ephemeral](/docs/actors/state#ephemeral-variables). If you wish to persist rate limits, you can optionally replace `vars` with `state`. ### Caching Tokens Cache validated tokens in `c.vars` to avoid redundant validation on repeated connections. See [ephemeral variables](/docs/actors/state#ephemeral-variables) for more details. ```typescript import { actor, UserError } from "rivetkit"; interface ConnParams { authToken: string; } interface ConnState { userId: string; role: string; } interface TokenCache { [token: string]: { userId: string; role: string; expiresAt: number; }; } // Example token validation function async function validateToken(token: string): Promise<{ sub: string; role: string } | null> { // In production, verify JWT or call auth service if (token.length > 0) { return { sub: "user-123", role: "member" }; } return null; } const cachedAuthActor = actor({ state: {}, createVars: () => ({ tokenCache: {} as TokenCache }), createConnState: async (c, params: ConnParams): Promise => { const token = params.authToken; // Check cache first const cached = c.vars.tokenCache[token]; if (cached && cached.expiresAt > Date.now()) { return { userId: cached.userId, role: cached.role }; } // Validate token (expensive operation) const payload = await validateToken(token); if (!payload) { throw new UserError("Invalid token", { code: "invalid_token" }); } // Cache the result c.vars.tokenCache[token] = { userId: payload.sub, role: payload.role, expiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes }; return { userId: payload.sub, role: payload.role }; }, actions: { getData: (c) => ({ userId: c.conn.state.userId }), }, }); ``` ## API Reference - [`AuthIntent`](/typedoc/types/rivetkit.mod.AuthIntent.html) - Authentication intent type - [`OnBeforeConnectContext`](/typedoc/interfaces/rivetkit.mod.OnBeforeConnectContext.html) - Context for auth checks - [`OnConnectContext`](/typedoc/interfaces/rivetkit.mod.OnConnectContext.html) - Context after connection _Source doc path: /docs/actors/authentication_