The JavaScript SDK is in pre-release and not on npm yet. Install it from source.
FolkwaysDocs

How authentication works

Tokens, signing keys and publishable keys — how your backend vouches for users, what a token contains and how the SDK keeps it fresh.

Every connection to Folkways presents a token: a short-lived, signed pass that says which segment the connection belongs to, who it is and what it may do. There are two ways to get one.

Signed by your backendTraded for a publishable key
Who it's forSigned-in users, agents, your serversVisitors who aren't signed in
IdentityYes, e.g. user:42None (anonymous)
Can publishYes, if its permissions allowNo, read-only
What your app holdsNothing secret: it asks your backend for a tokenThe publishable key (rte_pk_…), which is public
Set upA signing key on the segment, and a token endpointAnonymous connections on, and a publishable key
SDK optionauthUrl or tokenpublishableKey

Tokens signed by your backend

 Your app                     Your backend                         Folkways node
    │  POST /api/realtime-token   │                                       │
    │  (with your login cookie)   │                                       │
    │ ──────────────────────────► │ checks the user is signed in,          │
    │                             │ signs a token with the private key     │
    │ ◄────────────────────────── │                                       │
    │            { token }        │                                       │
    │ ─────────────── connect with the token ───────────────────────────► │
    │                             │        checks the signature with the   │
    │ ◄──────────────────────────────────────── session ───────────────── │  public key
  1. Your app asks your backend for a token, using your normal login (cookie or header).
  2. Your backend checks the user and signs a token with the segment's private signing key. With @folkways/realtime/server this is one call, and it makes no network request.
  3. The app connects with the token. The node checks the signature with the segment's public key, which it got from the dashboard.

The private key never leaves your backend, and nothing secret is ever sent over the network.

What a token contains

A token is a standard JWT. rte.createToken() fills it in for you; if you sign tokens with another JWT library, use these fields:

FieldWhereMeaning
kidheaderThe signing key's ID, as shown on the Signing keys tab
algheaderEdDSA (Ed25519), ES256 (P-256) or HS256 (shared secret)
segclaimsThe segment ID
subclaimsThe identity, e.g. user:42 (at most 256 bytes; must not start with anon:)
kindclaimsuser (default), agent or service
capclaimsPermissions: a list of { "topic": "<pattern>", "ops": [...] }
iat, expclaimsIssued and expiry time in seconds. At most 15 minutes apart

Permissions

A token lists topic patterns and the operations allowed on them:

capabilities: [
  { topics: 'chat/>', ops: ['subscribe', 'publish'] },  // read and write every chat room
  { topics: 'typing/>', ops: ['subscribe', 'signal'] }, // see and send typing indicators
  { topics: 'scores/*', ops: ['subscribe'] },           // read scores only
]
OperationAllows
subscribeReceiving a topic's events
publishPublishing events of any type: stream, kv and signal
setPublishing and removing kv values only
signalSending signals only
historyReading a topic's past events
presenceReserved for presence, which is coming soon

An operation must be allowed by both the token and the topic's rule in the dashboard. If either says no, the node answers Forbidden.

Grant each user only what they need, for example chat/team-7/> instead of chat/> for a member of team 7. In your app, session.can('publish', 'chat/lobby') tells you whether to show a send button. The node still enforces every permission.

Lifetime and refresh

Tokens live at most 15 minutes; createToken() makes them last 10 by default. You never handle expiry yourself:

  • The SDK asks for a new token about a minute before the current one expires, and swaps it on the open connection without dropping subscriptions.
  • If the node rejects a token, the SDK asks once more, with reason 'rejected'.
  • A plain string token can't be refreshed: the connection closes with AuthExpired when it runs out. Use it only for tests.

Signing keys

You add signing keys on the segment's Signing keys tab.

AlgorithmWhen to use
Ed25519 (EdDSA)The default. Fast and supported by most JWT libraries.
P-256 (ES256)When your library or key service doesn't support Ed25519.
Shared secret (HS256)Trying things out. One secret both signs and checks, so treat it like a password. Production plans need a key pair.

Rotating a key without downtime:

  1. Add a new key with a new ID. Both keys work side by side.
  2. Switch your backend to sign with the new key and its ID.
  3. After 15 minutes (the longest a token can live), delete the old key.

Deleting a key also closes every connection that is still using a token signed with it, which is how you cut off access quickly if a key leaks.

Publishable keys

A publishable key (rte_pk_…) is meant to be public: it ships inside your web page or app. The SDK trades it at the node for an anonymous, read-only token, so a copied key can never write to a topic or pose as a user. The worst it can do is read what your public page already shows.

  • It can grant only subscribe and history, on the topics you chose when creating it.
  • The segment must allow anonymous connections (Settings → Who can connect).
  • You can pause, resume or revoke it on the Publishable keys tab. Pausing or revoking also closes the connections using it.

See Publishable keys.

There are no secret API keys

Folkways has no secret key that your backend sends to get user tokens. Your backend signs tokens itself, which is faster and never puts a secret on the wire. Keys that start with rte_sk_ are refused by the node and the SDK.

Giving the SDK a token

Pass exactly one of these options to new Folkways.Realtime():

// 1. Your token endpoint. The SDK POSTs { reason } and reads { token } or a plain-text token.
new Folkways.Realtime({ url, authUrl: '/api/realtime-token' });

// 2. Your own function, for custom requests or token caching.
new Folkways.Realtime({
  url,
  token: async ({ reason }) => {        // reason: 'initial' | 'expiring' | 'rejected'
    const res = await fetch('/api/realtime-token', { method: 'POST', credentials: 'include' });
    return (await res.json()).token;
  },
});

// 3. A publishable key, for read-only apps without a backend.
new Folkways.Realtime({ url, publishableKey: 'rte_pk_…' });

// 4. A fixed token string. It can't be refreshed: tests and scripts only.
new Folkways.Realtime({ url, token: 'eyJhbGciOi…' });

authUrl sends your page's cookies when the endpoint is on the same origin. For an endpoint on another domain, or to send a header such as Authorization, use authHeaders or the token function.

Sign-in and sign-out

When the user signs in, signs out or switches account, give the client a new token source:

await client.updateToken(async () => fetchTokenForCurrentUser());
  • Same identity (for example, new permissions): the connection and subscriptions stay.
  • Different identity: the client reconnects as the new user, ends every subscription with IdentityChanged and wipes the previous user's local data.

To sign out completely, close the client and wipe its local data:

await client.close({ clear: true });

Catching a wrong segment

Pass the segment you expect to connect() to fail fast when, say, a staging token reaches production:

await client.connect({ segment: '5faeecf0-db6e-41a3-9448-afe1b096a6d2' }); // SegmentMismatch otherwise

On this page