Protect your realtime endpoints with custom authentication and authorization logic.
Basic Middleware
Add middleware to your realtime endpoint:
import { handle } from "@upstash/realtime"
import { realtime } from "@/lib/realtime"
import { currentUser } from "@/auth"
export const GET = handle({
realtime,
middleware: async ({ request, channel }) => {
const user = await currentUser(request)
if (!user) {
return new Response("Unauthorized", { status: 401 })
}
},
})
Middleware API
The middleware function receives:
The incoming HTTP Request object
The channel the client is attempting to connect to
- Return
undefined
or nothing to allow the connection - Return a Response
object to
block the connection with a custom error
Authentication Patterns
Verify user sessions before allowing connections:import { getSession } from "@/auth"
export const GET = handle({
realtime,
middleware: async ({ request }) => {
const session = await getSession(request)
if (!session?.user) {
return new Response("Please sign in", { status: 401 })
}
},
})
Validate JWT tokens or API keys:import { verifyToken } from "@/auth"
export const GET = handle({
realtime,
middleware: async ({ request }) => {
const token = request.headers.get("Authorization")?.replace("Bearer ", "")
if (!token) {
return new Response("Missing token", { status: 401 })
}
const payload = await verifyToken(token)
if (!payload) {
return new Response("Invalid token", { status: 401 })
}
},
})
Verify users can access specific channels:export const GET = handle({
realtime,
middleware: async ({ request, channel }) => {
const user = await currentUser(request)
if (channel === user.id) {
return
}
if (channel !== user.id) {
return new Response("You can only access your own channel", { status: 403 })
}
},
})
Control access based on user roles:export const GET = handle({
realtime,
middleware: async ({ request, channel }) => {
const user = await currentUser(request)
if (channel?.startsWith("admin-") && user.role !== "admin") {
return new Response("Admin access required", { status: 403 })
}
if (channel?.startsWith("team-")) {
const teamId = channel.replace("team-", "")
const isMember = await checkTeamMembership(user.id, teamId)
if (!isMember) {
return new Response("Not a team member", { status: 403 })
}
}
},
})
Custom Error Messages
Return detailed error responses:
export const GET = handle({
realtime,
middleware: async ({ request, channel }) => {
const user = await currentUser(request)
if (!user) {
return new Response("Authentication required", { status: 401 })
}
if (!user.isVerified) {
return new Response("Verify your email", { status: 403 })
}
},
})
Rate Limiting
Combine with Upstash Ratelimit for connection throttling:
import { Ratelimit } from "@upstash/ratelimit"
import { redis } from "@/lib/redis"
const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, "60 s"),
})
export const GET = handle({
realtime,
middleware: async ({ request }) => {
const ip = request.headers.get("x-forwarded-for") ?? "anonymous"
const { success } = await ratelimit.limit(ip)
if (!success) {
return new Response("Too many connections", { status: 429 })
}
},
})
Multi-Tenant Applications
To make sure users only access their organization’s data:
export const GET = handle({
realtime,
middleware: async ({ request, channel }) => {
const user = await currentUser(request)
const orgId = channel?.split("-")[0]
if (orgId !== user.organizationId) {
return new Response("Access denied", { status: 403 })
}
},
})