Sharing one browser login between AI agents: profiles, locks and refresh tokens
Why two agents cannot drive one Chrome profile, how to copy a signed-in session instead, and why the copies sometimes sign each other out.
You signed in to a web app once, and now you want three agents to use that login at the same time. The short answer: they cannot share one live browser profile, but they can each start from a copy of the signed-in state. The copies work until the site rotates its session, at which point they may start signing each other out. Which of those you hit depends on how the site handles sessions, and it is worth understanding before you build on it.
What a "login" is, from the browser's side
When you sign in, the site leaves state in your browser. Chromium keeps it in a user data directory, which, per the Chromium documentation, holds profile data such as history, bookmarks and cookies, with each profile in a subdirectory (often Default).
For signing in, the parts that matter are:
- Cookies. Most sites keep the session here, often as an opaque session ID or a signed token.
- Local storage and IndexedDB. Single-page apps often keep access tokens or refresh tokens here instead of cookies.
- Session storage. Scoped to one tab and not persisted. Playwright's authentication guide notes that it does not save session storage, and that sites rarely depend on it for sign-in.
On the server side there is usually a session record too, sometimes tied to a device, an IP range or a refresh token chain. That half is the one you cannot copy, and it is where most surprises come from.
Why agents cannot share one profile
The obvious idea is to point every agent's browser at the same profile directory. It does not work. Playwright's BrowserType documentation states it plainly: browsers do not allow launching multiple instances with the same user data directory. Starting a second one against a directory that is in use does not give you a second, independent browser.
Even if you forced it, it would be unsafe. Cookies and site storage live in database files that the running browser writes continuously. Two processes writing the same files would corrupt them or lose each other's updates. So the choice is between taking turns on one browser and giving each agent its own.
Option 1: one browser, agents take turns
One profile, one browser process, and a lock so that only one agent drives it at a time. This is the simplest correct design. The login is always current because there is only one copy of it.
The cost is throughput. While one agent is filling in a long form, the others wait. For an agent that checks a dashboard twice a day this is fine. For several agents working through queues, it becomes the bottleneck.
Option 2: clone the signed-in state
Sign in once, export the state, and start each agent's browser from its own copy. Playwright supports this directly with storageState, which its authentication guide says covers cookies, local storage, IndexedDB and passkey-based authentication. IndexedDB is opt-in, through the indexedDB option described in the BrowserContext documentation:
// After signing in once
await context.storageState({ path: "state.json", indexedDB: true });
// For each agent, a separate browser context starting from the copy
const agentContext = await browser.newContext({ storageState: "state.json" });Each agent now has an independent browser, so they can work in parallel. This works well for sites where a session is a long-lived cookie that the server does not change.
It breaks in two ways.
The copies drift. Each browser updates its own cookies as it goes. If the site refreshes the session in one browser, the others still hold the old value, and the saved file is stale too. Playwright's guide notes that you need to delete the stored state when it expires and authenticate again.
The copies collide. This is the less obvious one, and the next section explains it.
Why the copies sign each other out
Many sites, especially those using OAuth behind the scenes, use short-lived access tokens and a longer-lived refresh token. The OAuth security best practice, RFC 9700, section 4.14.2, requires authorization servers to detect refresh token replay for public clients such as browser apps. One of the two allowed methods is refresh token rotation: every time a refresh token is used, the server issues a new one and invalidates the old.
The RFC explains the consequence. If a refresh token is used by two parties, one of them will present a token that has already been invalidated. The server cannot tell which party is legitimate, so it revokes the active refresh token, and the legitimate client has to sign in again.
Now picture three agent browsers cloned from one signed-in state. They all hold the same refresh token. The first one to refresh gets a new token. The second one presents the old token, which the server reads as a replayed, possibly stolen token. Under the behaviour the RFC describes, the whole session is revoked, and all three agents are signed out, including the one that refreshed correctly.
From the outside this looks random: everything works for twenty minutes, then every agent lands on the sign-in page at once. If you see that pattern, rotation is the likely cause.
What to do about it:
- Use one account per agent where the service allows it. Playwright recommends the same for parallel tests that change server-side state: one account per worker. It also gives you a clean record of which agent did what.
- Let one browser own the session. Other agents work in their own browsers but get fresh state from the owner instead of refreshing on their own. This needs coordination, which is option 3.
- Prefer an API with its own credentials. If the app offers an API or an integration with scoped tokens, use that for agent work. A browser session meant for a person is the most fragile thing an agent can depend on.
Option 3: separate browsers, shared and versioned state
The design that holds up is a mix of the first two. Each active agent gets its own browser process, started from the latest saved state. When a browser finishes, or when a sign-in happens, its state is written back so later browsers start from it.
Two rules keep this safe:
- Save at quiet moments. Copy a profile only when its browser is not writing, for example after the agent finishes or the browser is paused. Copying a live database can capture it half-written.
- Version every save, and refuse to go backwards. Give each saved state a generation number. A browser that started from generation 4 must not overwrite generation 6 written by another agent in the meantime. This kind of check, often called fencing, is what stops a slow agent from undoing a fresh sign-in with its stale cookies.
It does not remove refresh token rotation, but it shrinks the window. Browsers started after a refresh pick up the new token instead of replaying the old one.
Treat saved state as a password
A saved browser state is a set of credentials. Playwright's guide warns that the file may contain sensitive cookies and headers that could be used to impersonate you, and strongly discourages committing it to any repository, private or public.
A short checklist before you share a login between agents:
- Store state encrypted, with the same care as an API key, and keep it out of logs and screenshots.
- Share only inside one trust boundary. If two agents should not see each other's data, they should not share a login. Give the sensitive one its own browser and its own account.
- Scope the account. Sign the agents in with an account that has only the access they need, not your administrator account, and decide which of its actions should wait for a person.
- Expect re-authentication. Sessions expire, rotate and get revoked. Build in a clear request to a person to sign in again instead of letting the agent retry a failing login, which can lock the account.
- Know what multi-factor prompts will do. An agent cannot approve a push notification on your phone. Sites that challenge new devices will challenge every fresh browser, so cloning state to a new machine can trigger a prompt each time.
Summary
| Approach | Parallel | Login stays current | Main risk |
|---|---|---|---|
| One browser, take turns | No | Yes | Throughput |
| Clone signed-in state | Yes | Until it rotates | Copies revoke each other |
| Separate browsers, versioned saves | Yes | Mostly | Complexity; rotation still possible |
| One account per agent | Yes | Yes | Needs more accounts |
If the service allows separate accounts, use them. If it does not, give each agent its own browser and treat the saved state as a versioned, encrypted secret.
Sources
- User data directory, Chromium
- Authentication and storage state, Playwright
- BrowserType: launchPersistentContext, Playwright
- BrowserContext: storageState, Playwright
- RFC 9700: Best Current Practice for OAuth 2.0 Security, section 4.14, IETF