Getting Started

Authentication

Authenticate and authorize Socket.IO clients using the nuxt-realtime:io hook.

Nuxt Realtime exposes the Socket.IO Server instance through the nuxt-realtime:io Nitro hook before any connections are accepted. Register io.use() middleware there to authenticate or authorize clients.

How it works

Create a Nitro server plugin and hook into nuxt-realtime:io. The hook fires after the Server is configured but before it is bound to the transport and before connection handlers are wired up, so every io.use() middleware registered here runs for every incoming connection.

server/plugins/realtime-auth.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('nuxt-realtime:io', (io) => {
    io.use((socket, next) => {
      // authenticate here: call next() to allow, next(new Error(...)) to reject
      next()
    })
  })
})

JWT-based authentication

Nuxt Realtime creates the client socket for you, so you can't call io({ auth: ... }) directly. Instead, register a nuxt-realtime:auth app hook. It fires before every connection attempt, including reconnects, so you can supply a fresh token each time instead of baking one in at initial connect:

plugins/realtime-auth.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('nuxt-realtime:auth', (ctx) => {
    ctx.auth.token = useAuthToken().value
  })
})

For a token that expires and needs refreshing on reconnect, fetch it inside the hook instead of reading a cached value:

plugins/realtime-auth.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('nuxt-realtime:auth', async (ctx) => {
    ctx.auth.token = await fetchFreshToken()
  })
})

Verify it on the server:

server/plugins/realtime-auth.ts
import jwt from 'jsonwebtoken'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('nuxt-realtime:io', (io) => {
    io.use((socket, next) => {
      const token = socket.handshake.auth?.token
      if (!token) return next(new Error('Unauthorized'))

      try {
        const payload = jwt.verify(token, process.env.JWT_SECRET!)
        socket.data.user = payload
        next()
      }
      catch {
        next(new Error('Unauthorized'))
      }
    })
  })
})

The middleware rejects unauthenticated connections before a connection event is ever emitted.

Read the session cookie from the underlying HTTP request exposed via socket.request:

server/plugins/realtime-auth.ts
import { parse } from 'cookie'
import { unsealData } from 'iron-session'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('nuxt-realtime:io', (io) => {
    io.use(async (socket, next) => {
      const cookies = parse(socket.request.headers.cookie ?? '')
      const sessionCookie = cookies['session']

      if (!sessionCookie) return next(new Error('Unauthorized'))

      try {
        const session = await unsealData(sessionCookie, { password: process.env.SESSION_SECRET! })
        socket.data.user = session.user
        next()
      }
      catch {
        next(new Error('Unauthorized'))
      }
    })
  })
})

Gating channels with socket.data

event:subscribe and event:publish (used by useRealtimeEvents) are handled internally and always join/broadcast — a handler you register for the same event name runs alongside the built-in one, not instead of it, so it cannot block the join. Gate channel access on your own custom events instead, or restrict who can connect at all via io.use() above.

Once authentication sets socket.data.user, use it in your own event handlers to guard access to your own channels:

server/plugins/realtime-channel-guard.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('nuxt-realtime:io', (io) => {
    io.on('connection', (socket) => {
      socket.on('room:join', (channel: string) => {
        const user = socket.data.user
        if (!canAccessChannel(user, channel)) {
          socket.emit('error', { message: 'Forbidden' })
          return
        }
        socket.join(`room:${channel}`)
      })
    })
  })
})
socket.data is typed as any by default. You can narrow it by extending Socket.IO's SocketData interface in a type definition file in your project.