Leveling Up Agent Browser: Giving Codex the Ability to Test Auth-Protected Pages
I gave my agent a tool to programmatically authenticate itself. Now it can navigate user pages and verify its work with a valid browser session.
One of the best things you can do to improve the quality of Claude Code or Codex is to give it a browser. Without one, it's essentially coding with its eyes closed and will frequently say a feature's complete but when you go to actually check, the UI's completely broken.
For frontend marketing pages that aren't protected behind user auth, using a browser is pretty easy. You download a tool like Vercel's Agent Browser or Playwright and Claude can use it out of the box.
But if you want your agent to test actual protected user flows within your application, it needs a way to obtain a valid session. Which is hard.
This is hard because your auth probably doesn't make it easy for bots to sign up for your platform (although check out the new auth.md for agent auth). Even if you had the agent go to your /login page, enter in a test email and password, and login like a normal user, this flow becomes a real testing bottleneck and wastes tokens.
We solved this for Speechbase by using passwordless OAuth (i.e. Magic Links) to programmatically generate validate sessions on demand. No browser login or human-in-the-loop required.
How it Works
Most cookie-session auth (WorkOS AuthKit, NextAuth, etc.) works like this:
- Your middleware reads a sealed cookie
- It unseals the cookie with a server-side secret
- The server accepts or rejects the request.
Forging that cookie from scratch is the tricky part. So instead of going through the normal login flow to create one, we can just ask our auth provider to produce one for us programmatically.
This can be done with the same technique that powers passwordless "Sign in via Email" auth. After a user enters their email, the app sends a short code to confirm the user owns that email address. The user then pastes that short-lived code on the login page, proving their ownership over that email and granting the user access to that email's account.
But instead of sending the code out-of-band to an email, we could just write a script that immediately validates the code and swaps it for a valid session for the provided email.
We use WorkOS AuthKit and it exposes this via a Magic Auth API:
createMagicAuth({ email })returns a one-time code directly in the API response. No email is delivered and it would be up to you to send that code to the user somehow.authenticateWithMagicAuth({ code, email, clientId, session: { sealSession: true, cookiePassword } })exchanges the code for asealedSessionbyte-equivalent to what the live login flow would set.
The cookie you get back is indistinguishable from a real session to your own middleware. NextAuth has getSessionCookie-style escape hatches, Lucia exposes createSession, and most providers have similar primitives.
The Example Script
Below is a script your agent can use to fetch a valid session from WorkOS:
import { execFileSync } from 'node:child_process';
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS(process.env.WORKOS_API_KEY!);
const EMAIL = 'agent-test-account@test.com'; // dedicated bot, not a real human
const BASE_URL = 'http://localhost:3001';
const { code } = await workos.userManagement.createMagicAuth({ email: EMAIL });
const { sealedSession } = await workos.userManagement.authenticateWithMagicAuth({
code,
email: EMAIL,
clientId: process.env.WORKOS_CLIENT_ID!,
session: {
sealSession: true,
cookiePassword: process.env.WORKOS_COOKIE_PASSWORD!,
},
});
// agent-browser is a CLI; this part swaps cleanly for Playwright's
// `context.addCookies()` or Puppeteer's `page.setCookie()`.
const run = (args: string[]) => execFileSync('agent-browser', args, { stdio: 'inherit' });
run(['open', BASE_URL]);
run(['cookies', 'set', 'wos-session', sealedSession, '--url', BASE_URL]);
run(['open', `${BASE_URL}/your-protected-route`]);
A Few Gotchas
Here's some things that we learned during the implementation:
- Cookie scope. The first unauthenticated
openredirects your browser to the auth provider's hosted sign-in domain. If you then callcookies setwithout specifying a target origin, the cookie attaches to that domain rather than your app so the next navigation to an auth-required route causes an infinite loop to re-authenticate. So always pin the cookie to a valid domain:--url http://localhost:3001(or Playwright'saddCookies([{ ..., url: 'http://localhost:3001' }])). - Don't cache the cookie. Sealed sessions expire so I was tempted to write the cookie to disk and reuse it across runs. I realized this was unnecessary because re-minting is cheap and not even possible in ephemeral environments like agents running in cloud sandboxes.
Required Pre-Setup
Since you'll be signing into a user account, you need to first create that user account in your auth provider. Name it something like: my-test-bot@test.com. And also enable magic auth.
You could let the agent use your own account/session cookie. Don't, because a dedicated bot identity helps keep logs and analytics separate from your own account, can have tighter permissions, and survives team turnover since no one's personal test session would need to be rotated if they leave the team.
The pattern beyond WorkOS and NextJS, just find your auth provider's "issue a session programmatically" API and treat it as a backend equivalent of the login form. Pin the result to your test browser's cookie jar and let the authenticated agent get to testing!