Authentication
This page shows how to authenticate a WebSocket connection from the browser, and how to keep credentials fresh across reconnects.
Browsers can't set headers
The browser WebSocket constructor takes a URL and optional sub-protocols. Nothing else. You can't add an Authorization header, so the token has to travel some other way:
A client running in Node can set headers. See Headers, proxies and TLS.
Token in the URL
Pass a function as the URL. It runs before every connection attempt, reconnects included, so each attempt can use a fresh token. It may be async:
The same works with the hook: useWebSocket(url) takes a string, so put the function in a module-level client as above and pass that to useWebSocket(live).
If the function throws or its promise rejects (for example the token refresh failed), that counts as a failed attempt. You get an error event (onError) with the thrown Error, and the client retries with the usual backoff. After reconnect.maxAttempts failures it gives up and the status becomes closed.
While the function is pending, the status is connecting (or reconnecting), and send() calls are queued as usual.
URLs often end up in server and proxy access logs. Prefer short-lived, single-purpose tokens over long-lived session tokens. See SECURITY.md.
Token as a sub-protocol
Some servers accept a token through the Sec-WebSocket-Protocol header, which you can set with protocols:
The server must echo back one of the protocols it accepts (usually v1), or the browser closes the connection.
protocols is fixed when the client is created, so this suits tokens that don't change during the client's life. For rotating tokens, use the URL function.
Cookies
The browser sends cookies with the WebSocket handshake, like any other request to that origin. If your API is on the same site as your app (for example app.example.com and api.example.com), session cookies authenticate the socket without any client code.
Check the Origin header on the server. Unlike fetch, WebSockets are not covered by CORS, so any site can try to open a socket to your server with the user's cookies.
First-message auth
Some protocols expect credentials as the first message after connecting. Send them from an open listener, which runs after every successful connection and reconnection.
Open listeners run before the send queue is flushed. A message sent synchronously from an open listener therefore goes out first, ahead of anything you queued while disconnected. Fetch the token in a url function, which runs before every attempt, so the open listener can send it without waiting:
await inside the open listener
If the listener awaits something before sending, the queue is flushed while it waits, and queued messages reach the server before auth. Get what you need beforehand, as above, or set queue: false so nothing is queued while disconnected.
With hooks, use onOpen. Prefer the module-level live.on('open', …) when several components use the client, so the auth message is sent once per connection rather than once per component:
If your server needs time to verify the credentials before it accepts other messages, wait for its acknowledgement (for example an auth-ok message) before sending application messages.
Stop retrying when credentials are rejected
Retrying with a rejected token is pointless. The simplest way to stop is to have the server close with 1008 (policy violation), which the client doesn't retry by default.
If your server uses a code of its own (in the 4000–4999 range), stop on that too. Combine it with retryableClose, the default rule, to keep stopping on 1008:
When shouldReconnect returns false, the status becomes closed and ready rejects, which a Suspense error boundary can catch.
A server that rejects first-message auth with some other close is still backed off: a connection counts as successful only once it stays open for minUptime (5 seconds by default), so each retry waits longer than the last.
Code 4000 is used by this library for a heartbeat timeout. Pick other codes for your own meanings.
Refreshing credentials
When the same user gets a new token (one was revoked), call reconnect(). It drops the current socket and connects again right away, calling your URL function for a new URL, and keeps what's queued:
reconnect() also restarts a client that gave up or was closed.
For a token that is only being renewed, call live.migrate() instead. It opens the new connection, with a new URL from your function, and closes the old one only once the new one is open, so the status stays open throughout. See Deploys.
Logging out and switching accounts
When the user logs out, or another user signs in on the same page, nothing the client kept for the previous user should reach the next one. live.close() closes the socket, stops reconnecting, and clears the send queue, a migrate() target and lastMessage. The managers built on the client keep their own state, so reset theirs too, then call connect() once the next user has signed in:
Reset only what your app uses. For an account switch, use close() and then connect(), not reconnect(), which keeps the send queue. With shared tabs, pass session to createTabsWebSocket, so the next user gets a connection of their own instead of the one the previous user authenticated.
Mounted components stay mounted; the status stays closed until connect() or reconnect() is called.
The server has a part too. A socket keeps the authority it was given when it connected until it closes, so when a session ends (a logout, possibly on another device, or a revoked session), close that user's open sockets on the server with a code your client doesn't retry, as the live notifications recipe does.