RFC: Role-Based Access Control for Motia Streams
Author: Sergio Marcelino
Status: Ready
Created: 2025-06-11
Target Release: v0.23.0
Topic: Streams, WebSockets, Authentication, RBAC
Summary
This RFC proposes an RBAC mechanism for the Motia Streams feature. Currently, any client can subscribe to any stream, with no enforcement of access rules. This proposal introduces a pluggable authentication entry point and per-stream authorization checks to enable secure, context-aware stream subscriptions.
Motivation
- Improve security by controlling stream access via authentication and authorization logic.
- Provide flexibility for developers to define custom access policies.
- Support anonymous access for public streams when desired.
Guide-Level Explanation
Client Message Format
Motia Client Library will send a header Authentication with the token in the WebSocket connection request.
Authentication: <token>
The Websocket server will then invoke the defined authentication function defined in the project before creating the connection.
Defining the authentication function
Developers need to define the authentication configuration in motia.config.ts using the streamAuth property.
The configuration should include:
contextSchema: A Zod schema defining the authentication context structureauthenticate: A function that receives the token as a string and returns aStreamAuthContextobject ornullif authentication fails
TypeScript Example
// motia.config.ts
import { config } from 'motia'
import { z } from 'zod'
// Define your Stream auth context model using zod
const streamAuthContextSchema = z.object({
userId: z.string(),
userName: z.string(),
userStatus: z.enum(['active', 'inactive']),
projectIds: z.array(z.string()),
})
export default config({
// ... other config options
streamAuth: {
contextSchema: streamAuthContextSchema,
authenticate: async (token: string) => {
// Implement your authentication logic here
// Example: verify JWT, check database, etc.
// returning null means the user is not authenticated and will be considered anonymous
// anonymous users can still have access to streams depending on the logic
return null
},
},
})
Motia framework will automatically create the StreamAuthContext type inside types.d.ts file for the project based on the contextSchema. From the example above, it should generate the following type:
interface StreamAuthContext {
userId: string
userName: string
userStatus: 'active' | 'inactive'
projectIds: string[]
}
Validating user access to a stream
Here's an existing stream definition:
import { StreamConfig } from 'motia'
import { z } from 'zod'
export const config: StreamConfig = {
name: 'message',
schema: z.object({
message: z.string(),
from: z.enum(['user', 'assistant']),
status: z.enum(['created', 'pending', 'completed']),
}),
baseConfig: { storageType: 'default' },
}
Users will be able to control whoever has access to a stream subscription using the canAccess function.
TypeScript Example
export const config: StreamConfig = {
name: 'message',
schema: z.object({
message: z.string(),
from: z.enum(['user', 'assistant']),
status: z.enum(['created', 'pending', 'completed']),
}),
baseConfig: { storageType: 'default' },
/**
* type Subscription = { groupId: string, itemId?: string }
* type StreamAuthContext depends on the contextSchema defined in motia.config.ts
*
* If this function is not defined, anonymous user has access to the stream
*
* Since we receive groupId and itemId, developers are able to give granular access to the stream
*
* @param subscription - The subscription context
* @param authContext - The authentication context
* @returns true if the user has access to the stream, false otherwise
*/
canAccess: (subscription: Subscription, authContext?: StreamAuthContext): boolean => {
return true
},
}