# Sessions and cookies

HTTP forgets you between requests, so a cookie carries an opaque reference that the server looks up to find who is calling. The cookie is a bearer credential: whoever holds it is treated as that signed-in person, which is why its attributes matter and why every request is still checked against what that caller may actually do.

- **Status:** Reference
- **Audience:** developer, both
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/technology/sessions-cookies/

## What is in the cookie, and what is not

The useful design is the boring one. The cookie holds an unguessable reference and nothing else; the record it points at — who signed in, when, which workspace, whether this is a customer browser — lives on the server. Nothing meaningful travels in the cookie, so nothing meaningful can be read out of it or edited in it, and ending somebody's access is a server-side act rather than a request that the browser please forget something.

| Attribute | Effect | What it does not do |
|---|---|---|
| `HttpOnly` | Script cannot read the cookie value | Does not stop script on the page causing requests that the browser attaches the cookie to |
| `Secure` | Sent only over HTTPS | Does not protect the value once it reaches a server or a proxy |
| `SameSite` | Limits sending on requests initiated by other sites | Not a substitute for a server-side check on state-changing requests |
| `Path` / `Domain` | Narrows where it is sent | Does not isolate one part of an application from another in any security sense |
| `Max-Age` / `Expires` | How long the browser keeps it | Does not control the real lifetime, which the server owns |

## A cookie says who, never what

This is the load-bearing sentence of the page. It identifies the caller. It authorises nothing. In Connect, what a caller may do is decided after identification and independently of it, three times over: the `customer_safe` allowlist refuses any `/api/*` path a customer browser is not permitted to call, the SQLAlchemy workspace kernel filters every query to the caller's workspace, and PostgreSQL row-level security filters it again in the database.

Each of those layers is capable of refusing a request on its own. That redundancy is the point: a mistake in a route, in a query, or in a new table's configuration is caught by a layer that was not part of the mistake. It is also why a customer browser hitting an Owner path returns 403 rather than data — `tenantAdapt` rewrites what it recognises and sends everything else to a blocked path that fails closed.

> **Note** A new scoped table gets row-level security applied by an operational step, not by creating the table. A table with a workspace column that was never added to the kernel's scoped list is filtered by nothing at all — the failure this three-layer design is most careful about.

## Does Connect use session cookies?

**Used.** A person signs in with Google and the browser then carries a session cookie, which is how the application knows on every subsequent request whether it is serving the Owner or a customer. There is no password login behind it and no alternative token a person can paste in.

Machine access does not use it. A developer or an agent authenticates with an integration key, which is issued and revoked on its own terms and is not tied to anybody's browser. Attempting to drive the API with a copied browser credential is unsupported and is exactly the pattern the layered checks are designed to make unproductive.

## Symptoms of an expired or mismatched sign-in

- **Screens render, and are empty.** An expiry produces 401 answers, and a screen showing you nothing is showing the absence of *data*, not the absence of records. Check who you are signed in as before investigating the feature.
- **One screen 403s and the rest are fine.** That is the allowlist or the path rewrite. The account is not restricted; the path has no customer form.
- **Signing in again on the same browser ended a colleague's.** Expected — replacement ends only what it replaced, on that browser, never their other devices.
- **An automated check reports signed-in forever.** A prober that treats 401 or a redirect as success will report health through a total sign-in outage. Assert on something only a signed-in caller can produce.

## Questions

### Does HttpOnly make a cookie safe from cross-site scripting?

It stops the value being read, which stops the cookie being exfiltrated. It does not stop injected script making requests from the page, which the browser will attach the cookie to. Script running on your page acts as you either way; `HttpOnly` limits the blast radius rather than removing it.

### Can I keep a session alive indefinitely for a background job?

That is the wrong tool. Sessions belong to people and to browsers. A long-running integration uses an integration key, which can be scoped and revoked without disturbing anyone's sign-in.

### Why is workspace membership not simply stored in the cookie?

Because then a stale cookie would carry stale authority, and revoking access would mean waiting for a cookie to expire. Resolving the workspace server-side on every request means a change of access takes effect on the next request, not eventually.

## Related

- [OAuth 2.0](https://connectbyjbrh.com/docs/technology/oauth/)
- [OpenID Connect](https://connectbyjbrh.com/docs/technology/openid-connect/)
- [HTTPS and TLS](https://connectbyjbrh.com/docs/technology/https/)
- [Authorisation models](https://connectbyjbrh.com/docs/technology/authorization-models/)
- [Security and isolation](https://connectbyjbrh.com/docs/security/)
- [Three independent layers of tenant isolation](https://connectbyjbrh.com/research/three-layers-of-isolation/)
- [A tenant facade that fails closed](https://connectbyjbrh.com/research/closed-by-default-facade/)

## What this page is based on

- Connect source pack (docs-source/sources/GENERAL.md §2, §3, §10) — request flow, RLS on new tables, session replacement
- Connect capability registry (docs-source/facts.py) — `session_replacement`, `rls_isolation`, `integration_keys`
- https://www.rfc-editor.org/rfc/rfc6265 — HTTP State Management Mechanism
