Access Rules
EdgeBase Realtime uses three distinct access models for three channel types. All channels default to authenticated users only — even in development mode.
Overview
| Channel Type | Rule Source | Evaluation Time |
|---|---|---|
Database subscriptions (onSnapshot) | Table's read access rule | Once at subscribe time |
| Presence channels | realtime.namespaces config | Once at subscribe time |
| Broadcast channels | realtime.namespaces config | Once at subscribe/publish time |
Database Subscriptions
Database subscriptions reuse the table's existing read access rule. No additional configuration is needed.
// edgebase.config.ts
export default defineConfig({
databases: {
shared: {
tables: {
posts: {
access: {
read(auth, doc) { return auth !== null }, // ← also used for onSnapshot
update(auth, doc) { return auth?.role === 'admin' },
},
},
},
},
},
});
When a client calls .onSnapshot(), the server evaluates the read rule once at subscribe time using partial evaluation — the auth parameter is fully available, but doc fields are not yet known (the Proxy returns true for all property access). This means:
- Rules that only check
authwork as expected:(auth) => auth !== null - Rules that check
docfields pass at subscribe time but are not re-evaluated per event
After subscription, every database change event is delivered to the subscriber. Use server-side filters for per-event filtering.
Presence & Broadcast Channels
Presence and Broadcast channels use realtime.namespaces in edgebase.config.ts with wildcard pattern matching:
export default defineConfig({
realtime: {
namespaces: {
// Presence: all channels require authentication
'presence:*': {
access: {
subscribe(auth) { return auth !== null },
},
},
// Broadcast: public channels anyone can listen, only auth can send
'broadcast:public-*': {
access: {
subscribe() { return true },
publish(auth) { return auth !== null },
},
},
// Broadcast: game channels require authentication for both
'broadcast:game-*': {
access: {
subscribe(auth) { return auth !== null },
publish(auth) { return auth !== null },
},
},
// Admin broadcast: only admins can subscribe
'broadcast:admin': {
access: {
subscribe(auth) { return auth?.role === 'admin' },
},
},
},
},
});
Rule Types
| Rule | Description | Signature |
|---|---|---|
subscribe | Who can join the channel and receive messages | (auth, channelId) => boolean |
publish | Who can send broadcast messages to the channel | (auth, channelId) => boolean |
publish rules are defined in configuration but are not currently evaluated at runtime. Only subscribe rules are enforced when a client connects. If you need to restrict who can publish to a channel, implement server-side validation in your application logic.
Wildcard Matching
The * wildcard matches any substring in the channel name:
| Pattern | Matches | Doesn't Match |
|---|---|---|
presence:* | presence:lobby, presence:game-1 | broadcast:lobby |
broadcast:public-* | broadcast:public-chat, broadcast:public-news | broadcast:private-chat |
broadcast:game-* | broadcast:game-lobby, broadcast:game-123 | broadcast:chat |
Default Policy
Channels with no matching namespace rule default to:
Authenticated users only — deny by default, regardless of
releasemode.
This means:
- In
release: true(production): unauthenticated users are denied - In
release: false(development): unauthenticated users are still denied
This is intentional — realtime channels should always require explicit security configuration.
Permission Re-evaluation
When a client refreshes their JWT token, the server automatically re-evaluates all active subscriptions:
- Client sends a new
authmessage with the refreshed token - Server verifies the new token and updates the user identity
- Server re-evaluates every subscribed channel against the new claims
- Channels that fail the re-evaluation are collected into
revokedChannels - Server sends
auth_refreshedresponse with the list of revoked channels - SDK automatically cleans up local subscriptions and fires
subscription_revokedevents
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#
- C++
client.realtime.on('subscription_revoked', ({ channel }) => {
console.warn('Lost access to channel:', channel);
// Optionally: show UI notification, redirect, etc.
});
client.realtime.on('subscription_revoked', (data) {
print('Lost access to channel: ${data['channel']}');
});
client.realtime.on("subscription_revoked") { data in
print("Lost access to channel: \(data["channel"] ?? "")")
}
client.realtime.on("subscription_revoked") { data ->
println("Lost access to channel: ${data["channel"]}")
}
client.realtime().on("subscription_revoked", data -> {
System.out.println("Lost access to channel: " + data.get("channel"));
});
client.Realtime.On("subscription_revoked", data => {
Console.WriteLine($"Lost access to channel: {data["channel"]}");
});
client.realtime().on("subscription_revoked", [](const json& data) {
std::cout << "Lost access to channel: " << data["channel"] << std::endl;
});
This process is graceful — the WebSocket connection stays open, and only the unauthorized channels are removed. All other subscriptions continue working normally.
Re-evaluation only happens at token refresh time (typically every 15–60 minutes). Between refreshes, the original permissions remain in effect. For immediate revocation, use the Instant Kick mechanism below.
Instant Kick
For emergency situations (account compromise, abuse, etc.), use the instant kick mechanism to forcefully disconnect a user:
- An admin action writes a KV blacklist entry for the user
- The server force-closes the user's WebSocket connection with a
FORCE_DISCONNECTmessage - When the client reconnects, the access rules re-evaluate and deny access
This is more aggressive than re-evaluation — the user's connection is immediately terminated rather than waiting for the next token refresh.
When to Use Each
| Scenario | Mechanism | UX Impact |
|---|---|---|
| Role change (e.g., downgrade from admin) | Re-evaluation | Smooth — specific channels removed silently |
| Account suspension / ban | Instant kick | Disruptive — WebSocket closed immediately |
| Normal JWT expiry | Re-evaluation | Transparent — SDK handles automatically |
| Security incident | Instant kick | Immediate — connection terminated |