Skip to content

REST Fallback for Realtime Broadcast Sends Empty Authorization Header, Causing 500 Error #1936

@uwuchanis73783

Description

@uwuchanis73783

Bug Report

I can confirm that this issue originates in Supabase itself and not in my application code. I have also reviewed the documentation, GitHub Discussions, and Discord prior to submitting this report.

Summary

When using @supabase/supabase-js to send a broadcast message through Realtime without subscribing to the channel first, the client switches to the REST fallback endpoint (/realtime/v1/api/broadcast).
If no auth session or access token exists, this fallback layer generates an empty Authorization header (literally Authorization: \r\n).
Realtime responds with HTTP 500, even though manually calling this endpoint without any Authorization header works and returns 202.

Key observations

  • The REST fallback always emits an Authorization header, even when no token is present.
  • If the session is absent, the header is sent with no value, instead of being omitted.
  • global.headers.Authorization is ignored by the REST fallback path.
  • Sending through WebSocket (after subscribe()) or explicitly calling realtime.setAuth(<token>) prevents the issue.
  • Making the same request manually with only the apikey header succeeds.

Steps to Reproduce

Minimal server-side example (Service Role key, no user session):

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!,
  {
    auth: { autoRefreshToken: false, persistSession: false },
    global: {
      headers: {
        apikey: process.env.SUPABASE_SERVICE_ROLE_KEY!,
        Authorization: `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY!}`,
      },
    },
  }
)

const ch = supabase.channel(`channel:${'some-channel-id'}`, {
  config: { private: true },
})

// No subscribe() call → triggers REST fallback
const ok = await ch.send({
  type: 'broadcast',
  event: 'new-message',
  payload: { hello: 'world' },
})

console.log('send ok?', ok)

Actual request produced by the client

POST /realtime/v1/api/broadcast
apikey: <service_role>
Authorization:
Content-Type: application/json

This results in:

  • HTTP 500 Internal Server Error
  • channel.send() returns { error: ... } without any detail about the 500 status.

Manual request that does work

await fetch(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/realtime/v1/api/broadcast`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    apikey: process.env.SUPABASE_SERVICE_ROLE_KEY!,
  },
  body: JSON.stringify({
    messages: [
      {
        topic: `channel:${'some-channel-id'}`,
        event: 'new-message',
        payload: { hello: 'world' },
        private: true,
      },
    ],
  }),
})

This returns 202 Accepted, and clients receive the broadcast.


Effective Workarounds

  • Call subscribe() and wait for the SUBSCRIBED status before calling .send().
  • Set the auth token explicitly via supabase.realtime.setAuth(<jwt>) server-side.

Both prevent the empty Authorization header from being emitted.


Expected Behavior

  • The REST fallback should omit the Authorization header if no token is available, instead of sending a blank one.
  • Alternatively, the fallback should respect global.headers.Authorization when no auth session exists.
  • The Realtime REST endpoint should return 400 or 401 for empty/invalid headers instead of 500.

This scenario is fairly easy to run into, especially when .send() is called before .subscribe(). The silent 500 makes debugging difficult, since the status is only visible when inspecting raw network traffic.


Possible Fixes

  1. In @supabase/supabase-js, avoid constructing an Authorization header when access_token is falsy (or use the value from realtime.getAuth() / global.headers).
  2. In Realtime, return a proper client error instead of a server error when receiving an empty or invalid Authorization header.

Library affected

supabase-js

Reproduction

No response

Steps to reproduce

No response

System Info

* **OS:** Windows 11 Home
* **supabase-js version:** 2.57.4
* **Node.js version:** 22.x
* **Environment:** server-side only, using Service Role key
* `auth.persistSession` is intentionally disabled, so the client has no session.

Used Package Manager

npm

Logs

No response

Validations

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsupabase-jsRelated to the supabase-js library.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions