Skip to content

Authentication

wrpc's auth story is three seams and one subpath that pre-wires them. This page is the map; the deep material lives where each seam is defined — Sessions for the server half, the client guide for the hooks.

The lifecycle

Three client options make it work end to end:

OptionWhen it runsWhat it is for
headersresolved on every openpresents the stored credential, so the server restores the session before any packet is dispatched
authenticateawaited on first connect and on every reconnect, before the restore re-sends subscriptionssigns in when there is nothing to present
refreshon a refusal with a listed code (calls and re-subscribes)single-flight credential renewal with a one-shot retry

The server half: token carriers

Where the token lives on the wire is the injected sessions.transport — the HttpOnly cookie is the byte-identical default. Two ready-made strategies ship in @alexify/wrpc/auth:

  • bearerTransport()Authorization: Bearer <token> on http/sse; on browser ws the token rides a wrpc.bearer.<token> subprotocol offer, a real upgrade header, so it never lands in the connect URL or the access logs that keep URLs.
  • payloadTransport({ field }) — a field of the declared meta bag, either x-wrpc-meta spelling.

Both are non-ambient (ambient: false): the credential is script-attached, so the safe-method CSRF rule the cookie needs does not apply to them.

The client half: stores and bearerAuth()

The same subpath carries token stores (get/set/delete, sync or async — a Map qualifies): memoryStore(), webStorage(localStorage), cookieStorage(document) (Secure by default). bearerAuth() composes a store with your signIn/refresh calls into the three options above:

js
const { bearerAuth, webStorage } = require('@alexify/wrpc/auth');

const client = await connect(url, {
  ...bearerAuth({
    store: webStorage(localStorage),
    signIn: (c) => c.call('auth/signIn', credentials()),
    refresh: (c, tokens) => c.call('auth/refresh', { token: tokens.refresh }),
  }),
});

See the full walk-through for the store contract, rotation and the server procedures these calls pair with.

What heals what

FailureWhat handles it
First connect, nothing storedauthenticatesignIn
Reconnect with a live stored tokenthe carrier restores the session before the re-subscribe — no hook code runs
A call refused 401/403 mid-sessionrefresh, single-flight; the call re-issues once
A re-subscribe refused after a long outagethe same refresh, then the subscription re-opens once — feeds heal exactly like calls
The refresh's own call refusedsurfaces its refusal (never deadlocks the run it belongs to); a throwing refresh clears the store, so the next connect signs in fresh

Observability: a failing run logs refresh.failed, emits 'refresh-failed' and counts on wrpc.client.refreshes — see Telemetry.

Released under the MIT License.