All pages
On this page

The access token and who can reach the relay

A relay has one owner and one access token. Whoever holds the token has full control of the relay and, through its agents, of the user account the relay runs as. There are no user accounts, roles or per-device keys. This page says where the token comes from, how a browser or a script presents it, who can connect at all, what is reachable without it, and what to do if it leaks.

The token #

oar setup creates the token (a random one for a new config, 43 characters of random letters, digits, - and _) and writes it to authToken in the config file, config.json in OAR's state folder. The relay keeps that file readable by you only, outside Windows. See config.json and Where secrets live.

Without an authToken in the config, the relay makes a random token on every start, prints it to its log and does not save it. Start the relay with --token <value> and that token is used for the run only; it is never saved, and it is part of the command line. Your agents read the token from config.json (see Agents and the token), so use a token that is saved there if agents are to work.

oar url prints the address with the current token, for example http://localhost:3333/?token=<your token>. Treat that address like a password.

Ways to present it #

Way Used by
The ?token= part of the page's address A browser, the first time. The page takes the token out of the address bar and sends it to the relay in a header; the relay answers with the sign-in cookie below. Added in 0.9.11 It works on the page address only: API, file and asset addresses do not accept a ?token=
The sign-in box, Enter your access token to connect, then Connect A browser that has no cookie. A wrong token shows Invalid token; an empty box shows Please enter a token
An Authorization: Bearer <token> header Scripts and API calls
The sign-in cookie A browser that signed in before

The token is never read from a request body or, since 0.9.11, from the query of a request. If a request carries both a header and the cookie, the relay uses the header. A wrong value is not skipped: a wrong header is refused even when the cookie is right. The live connection (Socket.IO) reads the token from the connection's auth.token field or from the cookie, not from a header and not from its query.

A token in the address

The first request for the page, /?token=..., carries the token in its address. So the token can end up in the browser's history, in the logs of a reverse proxy or tunnel in front of the relay, and in anything you copy the address into. The page removes it from the address bar after loading and signs in with a header, and no other request takes the token from the address. Prefer the sign-in box or a header where you can.

Scripts that used ?token=

Up to 0.9.10 the relay accepted ?token= on every request. A script that fetches API, file or asset addresses with it now gets 401: send Authorization: Bearer <token> instead. A Socket.IO client passes the token as auth.token. See Upgrade notes.

After a valid token, the relay sets a cookie named copilot_auth that lasts 30 days. This also happens for a script that sends the token in a header. It is HttpOnly, so page scripts cannot read it, and SameSite=Lax. It is marked Secure when the relay is reached over HTTPS. If a request arrives with a cookie that no longer matches the token, the relay answers 401 and clears the cookie, and the browser shows the sign-in box. The cookie holds the token itself.

Signing out and revoking a device #

The app has no sign-out button for the relay, and the relay keeps no list of signed-in devices. Because the cookie holds the token itself, one device cannot be revoked on its own. On your own browser, clear the cookie in the browser's site settings. To lock out every device, including one you lost, change the token (next section).

Rotating the token #

oar setup --new-token

This writes a new token and restarts the relay (in a terminal it asks first; see oar setup). What happens:

  • Signed-in browsers. The old token stops working when the relay has restarted. The restart drops the live connection, and the next request of each browser fails: the relay clears its cookie and the browser shows the sign-in box. Sign in again with the address from oar url or the new token.
  • Until the restart. If you decline the restart, the running relay keeps accepting the old token until oar restart.
  • Paired relays. A relay that has your token stored, because you paired the two, gets Token not accepted. Add the relay again there with the new token; see Remote relays. Removing a pairing on this relay does not revoke a token the other side already holds, so rotating is how you lock that relay out.
  • relay-previous.json. While a changed port or token waits for the restart, oar setup keeps the old port and token in this file in the data folder (plain text, readable by you only), so that oar status, oar stop and oar restart still find the running relay. It is not a grace period: the file changes nothing about what the relay accepts, and the oar commands delete it once the relay answers on the new settings. The old token stops working only when the relay restarts.
  • Share links and preview links have their own tokens and are not affected.

Editing authToken in the file by hand works the same way; oar restart applies it.

Who can connect at all #

localhostOnly in the config decides where the relay listens:

localhostOnly Setup option The relay listens on Reachable from
true (the default) oar setup --local, or answer 1 127.0.0.1 This machine only
anything else oar setup --lan, or answer 2 0.0.0.0 (all interfaces) Every device that can reach the machine's address and port

Only the boolean true and the text "true" (any case) count as local. false, null, 1, "yes" and every other value open the relay to the network; the default applies only when the key is missing. oar setup writes the key as true or false. A change needs oar restart.

oar setup, oar url and oar status read the key differently: only the boolean false counts as LAN access for them, and oar doctor shows localhostOnly: no only for false. With a value such as null or "no" the relay listens on all interfaces while those commands report this machine only. Keep the key to true or false. See config.json.

On a LAN, the token is the only gate: the relay does not check where a request came from. With access this machine only, a phone cannot connect until you switch on LAN access or use a tunnel. localhostOnly does not affect the managed tunnels, which connect outward from the machine.

A relay that sits behind a proxy or tunnel on the same machine can stay this machine only. Which proxies' X-Forwarded-* headers the relay believes is set by trustProxy (default loopback); that decides whether it treats a connection as HTTPS and marks the cookie Secure.

What needs the token #

Everything that reads or changes your conversations, files, settings or agents needs the token. These are the exceptions, and the whole list.

Path Needs the token? What it gives
/ and /index.html No The web app and its sign-in box
The web app's script, style and icon files, /sw.js, /socket.io/socket.io.js No The app itself, which holds none of your data
/manifest.webmanifest No The install manifest. It carries the app name you set in Settings
/shared/<link token> No The page of a shared conversation
/api/shared/<link token> and its presence, upload and generated-image routes No, but a valid, unrevoked link token Read-only access to that one conversation. See Sharing
Preview links on the preview host No, but a valid preview token The previewed app only; the preview host does not serve the relay. See Previews
/api/... (all other routes) Yes Conversations, settings, files, status, previews, pairing
The live connection (Socket.IO) Yes, and Added in 0.9.11 a browser must open it from the relay's own address The app's live updates. See allowedOrigins for a proxy that rewrites the host
The workers' connection to the relay Yes Used by your agents' own processes on this machine
Files from your drives and workspace, uploads and generated images by their own address Yes A shared viewer cannot load them this way; the shared routes above serve the ones that conversation uses. See Media and files

There is no unauthenticated health or version endpoint: /api/status, which oar status calls, needs the token.

Share links and preview links are public by design. The link's random token is its only protection, so close or revoke them when you are done. Files the relay serves are sent with a sandbox policy, so a served HTML or SVG file cannot run script on the relay's origin.

The workers' connection only answers requests from the relay's own machine that no proxy or tunnel forwarded; anything else is refused with 403 before the token is checked. Shared links through a tunnel are not affected. See The session-worker path guard.

Protections on sign-in #

  • For sign-in and the live connection the relay compares tokens in constant time.
  • Added in 0.9.11 Ten wrong tokens lock an address out for a minute. Ten wrong tokens from one client address within a minute, in a header, the cookie, the sign-in box or a live-connection handshake, make the relay answer that address 429 with Retry-After until the minute is over, the right token included. The sign-in box says Too many failed sign-in attempts. Try again in 42 seconds. The same wrong value repeated counts once, so a tab with an old cookie does not lock you out. The relay logs a line starting with [auth] when it refuses an address.
  • Requests from the relay's own machine that no proxy or tunnel forwarded are never counted and never locked out, so the relay's workers and the agents' tools keep working.
  • The client address is the one the relay trusts. Behind a proxy on another machine that the relay does not trust, every client has the proxy's address, and one client's wrong tokens lock all of them out.
  • The lockout slows guessing; it does not replace a good token. Keep the token long and random: oar setup generates one; do not replace it with a short word.
  • The one rate limit on routes that need no token is for shared-conversation presence pings: 24 per 10 seconds for each share link and client address. Added in 0.9.11 The client address follows trustProxy, like the lockout, so a client cannot pick its own by sending X-Forwarded-For.
  • The token is not written to the log, except when the config has none and the relay makes a temporary one at start. oar start and oar url print it to your terminal.

Agents and the token #

Your agents run as processes of the user the relay runs as, on the relay's machine. They do not get credentials of their own: to talk to the relay they read the relay's own token from config.json and send it as the owner would. The relay does not put the token in an agent's environment or prompt, but an agent runs as the same operating-system user, so it can read the file and the token with it. The same goes for anything an agent runs. Treat the token as known to every agent you run, and run agents only as a user you trust with the relay.

The relay does not tell agents the token of a paired relay: agents reach a paired relay through this relay, which adds the stored token (Remote relays). An agent also acts with the permissions of the user the relay runs as, so anything the relay's owner can do on the machine, an agent can be asked to do.

Plain HTTP on a LAN #

With LAN access and an http:// address, the token and the cookie cross the network unencrypted. Anyone who can watch that traffic can read the token. Use it on a network you trust, or put HTTPS in front. The cookie is not marked Secure on plain HTTP.

Installing OAR as an app and push notifications need HTTPS or localhost, so neither works on a plain LAN address: see Install as an app and Notifications.

If you think the token leaked #

  1. Run oar setup --new-token and let it restart the relay. Every browser is signed out. If you decline the restart, run oar restart: until then the old token still works.
  2. Sign in again with the address from oar url.
  3. On every relay that you paired with this one, add this relay again with the new token (Remote relays).
  4. Check where the token was stored: scripts, shell history, the address you pasted into a chat, and copies or backups of config.json.
  5. Stop or renew shared links in Settings → Sharing (Your shared links) and close previews you do not recognize. Those have their own tokens.
  6. If someone could have used the relay while it was exposed, look at what its agents did. A token holder can start agents that run commands as the relay's user.