Authentication
Zero uses a JWT-based flow to authenticate connections to zero-cache.
Frontend
During login:
- Your API server creates a
JWTand sends it to your client. - Your client constructs a
Zeroinstance with this token by passing it to theauthoption.
const zero = new Zero({
...,
auth: token, // your JWT
userID, // this must match the `sub` field from `token`
});
Server
For zero-cache to be able to verify the JWT, one of the following environment variables needs to be set:
ZERO_AUTH_SECRET- If your API server uses a symmetric key (secret) to create JWTs then this is that same key.ZERO_AUTH_JWK- If your API server uses a private key to create JWTs then this is the corresponding public key, in JWK format.ZERO_AUTH_JWKS_URL- Many auth providers host the public keys used to verify the JWTs they create at a public URL. If you use a provider that does this, or you publish your own keys publicly, set this to that URL.
Refresh
The auth parameter to Zero can also be a function:
const zero = new Zero({
...,
auth: async () => {
const token = await fetchNewToken();
return token;
},
userID,
});
In this case, Zero will call this function to get a new JWT if verification fails.
Opaque Auth Tokens
If you use only custom mutators and synced queries, you can use an opaque token instead of a JWT. This is because in this case Zero's permission system is not used and thus Zero doesn't need to be able to decode your auth token. In this case, you do not need to set ZERO_AUTH_SECRET, ZERO_AUTH_JWK, or ZERO_AUTH_JWKS_URL on the server.
You will still set the auth parameter to Zero on the client as described above, but Zero won't try to decode or validate it. Zero will simply forward it to your server in the Authorization header.
Cookie-Based Auth
Restricting yourself to custom mutators and synced queries also enables standard cookie-based auth.
To do this, you'll need zero-cache and your server to run on the same domain so that they can share cookies. This happens automatically for dev as long as your frontend and zero-cache are both running on localhost with different ports.
For production you'll need to do two things:
- Run
zero-cacheon a subdomain of your main site (e.g.,zero.example.comif your main site isexample.com). Consult your hosting provider's docs, or your favorite LLM for how to configure this. - Set cookies from your main site with the
Domainattribute set to your main domain (e.g.,.example.com). If you use a third-party auth provider, consult their docs on how to do this. For example, for Better Auth, this is done with thecrossSubDomainCookiesfeature.
Once you've done these two steps, tell zero-cache to forward cookies to your server by setting these two config vars:
ZERO_GET_QUERIES_FORWARD_COOKIES=true
ZERO_MUTATE_FORWARD_COOKIES=true
From there, you can authenticate the user using cookies the same way you would for any other request to your server.
Client-Side Data Storage
Zero stores client-side data in IndexedDB by default, but this is customizable with the kvStore parameter:
const zero = new Zero({
// Store data in React Native's SQLite database
// See https://zero.rocicorp.dev/docs/react-native
kvStore: expoSQLiteStoreProvider(),
});
const zero = new Zero({
// Store data in memory, it disappears on refresh
kvStore: 'mem',
});
Because multiple users can share the same browser, Zero requires that you provide a userID parameter on construction:
const zero = new Zero({
...,
userID: "user-123",
});
Zero stores each user's data in a different IndexedDB instance. This allows users to quickly switch between multiple users and accounts without resyncing.
If your application is unauthenticated, or if you don't need fast user switching, you can just set userID to a constant like anon or guest:
const zero = new Zero({
...,
userID: "anon",
});
Alternately, if you have more than one set of Zero data per-user (i.e., for different apps in the same domain), you can additionally use the storageKey parameter:
const zero = new Zero({
...,
userID: "user-123",
storageKey: "my-app",
});
If specified, storageKey is concatenated along with userID and other internal Zero information to form a unique IndexedDB database name.

Zero's IndexedDB databases are prefixed with 'rep' or 'replicache' because ... well because we haven't fixed this yet.
Logging Out
When a user logs out, you should consider what should happen to the synced data.
If you do nothing, the synced data will be left on the device. The next login will be a little faster because Zero doesn't have to resync that data from scratch. But also, the data will be left on the device indefinitely which could be undesirable for privacy and security.
If you instead want to clear data on logout, Zero provides the dropAllDatabases function:
import {dropAllDatabases} from '@rocicorp/zero';
// Returns an object with:
// - The names of the successfully dropped databases
// - Any errors encountered while dropping
const {dropped, errors} = await dropAllDatabases();
Permissions
Any data placed into your JWT (claims) can be used by permission rules on the backend.
const isAdminRule = (decodedJWT, {cmp}) => cmp(decodedJWT.role, '=', 'admin');
See the permissions section for more details.
Examples
See zbugs or hello-zero.