#Can I control HTTP-Only Cookies? In queries, can I use Cookies from the websocket handshake?

5 messages · Page 1 of 1 (latest)

delicate cradle
#

I'd like to avoid storing authentication tokens in places like localStorage which aren't encrypted-at-rest and which are vulnerable to exfilteration in the event of XSS / malicious browser extensions / etc.

I believe I can read and write the Cookies header from an HTTP Action in Convex -- is that correct? If so, I could write a HTTP action which saves the token as a HTTP-Only cookie, protecting it from many of these situations.

However, is that HTTP-Only cookie going to be available in subsequent query and mutation calls made through the Convex client?
My understanding is that Cookies are sent normally in the initial HTTP upgrade request for establishing a Websocket, but are they saved and made available to my handler functions?

valid saffronBOT
#

Hi @delicate cradle. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

There isn’t enough information in the knowledge sources to fully support using HTTP-only cookies as an auth mechanism for Convex queries/mutations over the WebSocket, so the answer below focuses on what is documented.

  1. HTTP actions and cookies

    HTTP actions receive a standard Request object and return a Response, so you can read cookies from the request headers and set cookies on the response (including HttpOnly) just like in any Fetch-based handler. [HTTP actions]

    import { httpAction } from "./_generated/server";
    
    export const setCookie = httpAction(async (ctx, request) => {
      const res = new Response(null, { status: 200 });
      res.headers.append(
        "Set-Cookie",
        "my_token=abc123; HttpOnly; Path=/; Secure; SameSite=Lax"
      );
      return res;
    });
    

    So your idea of using an HTTP action to write an HTTP-only cookie is supported.

  2. Cookies and WebSocket-based queries/mutations

    The knowledge sources explicitly note that WebSocket clients don’t get server-only cookies automatically, and that this is one of the reasons Convex Auth exposes tokens to client JS instead of relying on cookies. [Session tracking; Auth security]

    Key points from those docs:

    • WebSocket messages don’t carry browser cookies the way normal HTTP requests do. [Session tracking]
    • Convex Auth therefore stores refresh/access tokens in client-accessible storage (by default localStorage) and sends the JWT over the WebSocket. [Auth security]
#

The docs do not state that cookies from the initial WebSocket upgrade are persisted or exposed to ctx in query/mutation handlers, and they explicitly position cookie-based approaches as not working well with the Convex WebSocket client.

So, based on the available docs:

  • You can use HTTP-only cookies for HTTP actions (e.g., for webhooks or a separate HTTP API).
  • There is no documented way to have Convex queries/mutations (over the WebSocket) automatically read or use those HTTP-only cookies, nor to access cookies from the WebSocket handshake inside ctx in queries/mutations.
  1. Recommended patterns in the docs

    Because of these WebSocket limitations, the official materials recommend:

    • Passing tokens explicitly as arguments or via wrappers (e.g., custom fetchQuery wrappers, session wrappers) rather than relying on cookies. [Sessions wrapper; Token wrapper suggestion]
    • For non-user/server contexts, using a shared secret or API key passed as an argument and checked in the function. [Server secret]
    • In Convex Auth / Better Auth integrations, using localStorage (or a pluggable storage) for tokens, with strong emphasis on preventing XSS. [Auth security]
#

Given the current docs, you can’t rely on HTTP-only cookies as a transparent auth channel for WebSocket-based queries/mutations. If you need stronger protection than localStorage, you’d have to implement a custom pattern (e.g., server-side proxy that reads cookies and calls Convex with a shared secret), but that pattern is not described in the provided sources, so I can’t give a documented recipe for it.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: