Skip to main content
Protect your realtime endpoints with custom authentication and authorization logic.

Basic Middleware

Add middleware to your realtime endpoint:
api/realtime/route.ts
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:
request
Request
The incoming HTTP Request object
channel
string
default:"default"
The channel the client is attempting to connect to
return
  • 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:
api/realtime/route.ts
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:
api/realtime/route.ts
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:
api/realtime/route.ts
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:
api/realtime/route.ts
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:
api/realtime/route.ts
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:
api/realtime/route.ts
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:
api/realtime/route.ts
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 })
    }
  },
})