Agentic Browser Login Flows: FedCM, Passkeys/WebAuthn, and 2FA‑Safe Pipelines for Auto‑Agent AI Browsers
Agentic browsers are crossing from demos into production workflows: research copilots logging into journals, sales agents filing updates in CRMs, and QA bots performing end‑to‑end checks. The bottleneck isn’t DOM clicking; it’s authentication. Modern login stacks—federation via FedCM, passkeys/WebAuthn, step‑up 2FA, device‑bound sessions, and anti‑bot challenge layers—were all built to keep humans safe and botnets out. That’s a good thing. But it means “just automate sign‑in” can backfire, be brittle, or worse, violate terms or security expectations if done naively.
This article gives a practical, opinionated drill‑down for building a compliant, 2FA‑safe sign‑in pipeline for agentic browsers—covering Federated Credential Management (FedCM) prompts, WebAuthn/passkeys, SMS/email OTP handling, device‑bound cookies and session refresh, anti‑bot challenge alignment, and deterministic replay without credential exposure. It targets builders of internal agents, test automation, and first‑party bots for properties you own or have explicit authorization to automate.
The thread throughout: cooperate with the platform’s security model. Avoid credential exposure. Prefer first‑party APIs and official flows to brittle DOM scraping. And be explicit about the human‑in‑the‑loop boundaries when user presence/verification is required.
Why agentic browser login is uniquely hard
- Web is moving to privacy‑preserving identity and anti‑abuse:
- Third‑party cookies are deprecated; federated login is moving to FedCM.
- Anti‑bot challenges increasingly rely on attestation and behavior signals instead of simple CAPTCHAs.
- Sessions are becoming device‑bound to mitigate cookie theft.
- Strong authentication is the norm:
- Passkeys/WebAuthn require user presence (UP) and often user verification (UV).
- 2FA is ubiquitous; some factors are intentionally non‑automatable.
- Browser automation runs into platform guardrails:
- FedCM prompt UI is browser‑owned, not DOM.
- WebAuthn authenticators don’t expose private keys to scripts.
- Challenge/attestation systems can detect headless patterns.
The result: robust login automation requires a cooperative design across product, auth, and automation—not just a headless driver.
Design principles for 2FA‑safe agentic sign‑in
- Respect the security model
- Only automate on properties you own or have been explicitly authorized to automate.
- Do not bypass anti‑bot or 2FA protections; integrate with supported mechanisms.
- Separate duties
- Split the “secure auth broker” (which can see secrets) from the “agent browser” (which should not). Use capability tokens instead of raw credentials.
- Minimize secret surface area
- No passwords in code or logs. Use secret managers and ephemeral tokens. Redact HAR traces.
- Prefer official APIs
- For identity: FedCM/OIDC, OAuth device flow, service accounts. For testing: DevTools virtual authenticators only in pre‑prod.
- Determinism and replay under test conditions only
- Deterministic replay is fine for staging with virtual authenticators and synthetic OTP; in production, treat authentication as non‑deterministic and user/event driven.
- Human‑in‑the‑loop for presence/verification
- When UP/UV is required (passkeys), build a user confirmation step to mint a delegated session for the agent.
A reference architecture
+-------------------+ +---------------------------+ +----------------------+
| Orchestrator/LLM | RPC | Secure Auth Broker (SAB) | OIDC | Identity Provider |
| (agent brain) +-------->+ - FedCM/OIDC client +------->+ (IdP / FedCM IdP) |
| | | - OTP inlets (email/SMS) | +----------------------+
| | | - Passkey delegation |
+--------+----------+ +-----+---------------------+
| |
| | capability tokens / session lift
| v
| +------+--------------------+
| | Agent Browser Runtime |
| | (Playwright/Puppeteer) |
| | - No raw credentials |
| | - HAR redaction |
| +------+--------------------+
| |
| v
| +------+-----------------+
| | Target RPs / Sites |
| +-----------------------+
- Secure Auth Broker (SAB) handles interaction with IdPs, OTP channels, and passkey delegation. It produces a scoped, time‑boxed capability (session cookie, OIDC token, or session lift) for the agent browser.
- Agent browser runs with minimal privileges and no knowledge of master secrets.
- Observability is split: fine‑grained event logs in SAB, high‑level task logs in the agent. Sensitive payloads are structured, redacted, and encrypted.
FedCM: handling federated prompts the right way
FedCM (Federated Credential Management) moves federated sign‑in UI and decisions into the browser, reducing cross‑site tracking and phishing risk. RPs call navigator.credentials.get({ identity: ... }); the browser shows an account chooser and issues tokens by talking directly to the IdP. The page itself doesn’t see third‑party cookies or raw federated assertions.
Key implications for agents:
- The FedCM prompt is browser UI, not DOM; it won’t show up in
document.querySelector. - Headless testing can use DevTools Protocol’s FedCm domain to observe and select accounts, but this is intended for testing and may be gated/flagged.
- In production automation, prefer pre‑authenticated sessions or service tokens instead of trying to “click FedCM.”
Practical patterns
- For first‑party RPs you control: build a “session lift” endpoint. A human authenticates via FedCM in a normal browser, then clicks “Authorize agent,” which issues a same‑site, time‑boxed session for the agent user‑agent. No agent sees the FedCM UI.
- For test environments: use Chrome DevTools Protocol (CDP) FedCm domain to drive the dialog deterministically.
- For third‑party RPs: if they support OIDC device flow or OAuth, prefer those flows. Avoid DOM automation of FedCM on sites you don’t control.
Example: selecting a FedCM account in test (Chromium, Puppeteer + CDP)
Note: This is for CI/testing in your own staging environments. The FedCM DevTools domain and permissions may change; consult current DevTools Protocol docs.
jsimport puppeteer from 'puppeteer'; (async () => { const browser = await puppeteer.launch({ headless: 'new' }); const page = await browser.newPage(); // Create a CDP session to control FedCM test hooks const cdp = await page.target().createCDPSession(); // Enable FedCM devtools domain (for testing only) await cdp.send('FedCm.enable'); // Optionally, grant permission if your test needs it // await cdp.send('Browser.grantPermissions', { permissions: ['fedcm'], origin: 'https://rp.example.test' }); cdp.on('FedCm.dialogShown', async (evt) => { // evt.accounts = [{ accountId, email, name, picture }, ...] const first = evt.accounts[0]; console.log('FedCM dialog shown with accounts:', evt.accounts.map(a => a.email)); await cdp.send('FedCm.selectAccount', { dialogId: evt.dialogId, accountId: first.accountId }); }); await page.goto('https://rp.example.test/login'); await page.waitForNavigation({ waitUntil: 'networkidle0' }); // Persist the authenticated storage for reuse (test only) const client = await page.target().createCDPSession(); const storage = await client.send('Storage.getStorageKeyForFrame', { frameId: page.mainFrame()._id }); // In practice, use Playwright/Puppeteer storage state export helpers instead await browser.close(); })();
Caveats:
- Treat FedCM CDP hooks as test‑only. Avoid shipping production agents that "click" FedCM prompts.
- If you operate both RP and IdP in test, you can preconfigure accounts for deterministic selection.
Passkeys/WebAuthn: handling user presence and verification
Passkeys are built on WebAuthn. They are excellent for security precisely because they require user presence (touch/biometric) and keep private keys locked in authenticator hardware or platform keystores. Automation has to respect this:
- In production, you cannot and should not extract passkey private material.
- Automation cannot fake user presence on real authenticators.
- DevTools virtual authenticators exist for testing only.
Patterns that work in practice
- Delegated session after a human UP/UV
- A human signs in once with a passkey. Your RP issues a short‑lived, scope‑limited agent session (e.g., via a first‑party “Authorize Agent” button). The agent uses that cookie/token; it never touches WebAuthn.
- Proof‑of‑possession tokens (when using OAuth/OIDC)
- If your RP is an OAuth client, prefer a back‑channel token mint with DPoP/MTLS to bind the token to a client key rather than a browser cookie. The SAB does the mint; the agent gets a bound token.
- Virtual authenticators and synthetic credentials in CI
- For staging, use CDP WebAuthn domain to add a virtual authenticator and pre‑register a synthetic credential for deterministic login tests.
Example: WebAuthn virtual authenticator in CI (Chromium + CDP)
jsimport puppeteer from 'puppeteer'; (async () => { const browser = await puppeteer.launch({ headless: 'new' }); const page = await browser.newPage(); const cdp = await page.target().createCDPSession(); await cdp.send('WebAuthn.enable'); const { authenticatorId } = await cdp.send('WebAuthn.addVirtualAuthenticator', { options: { protocol: 'ctap2', transport: 'usb', hasResidentKey: true, hasUserVerification: true, isUserVerified: true } }); // Optionally, pre-provision a resident credential for deterministic login flows // (Shape simplified; consult DevTools Protocol docs for exact fields.) await cdp.send('WebAuthn.addCredential', { authenticatorId, credential: { credentialId: 'BASE64URL_ID', rpId: 'rp.example.test', privateKey: 'PEM_OR_DER_PRIVATE_KEY_FOR_TEST_ONLY', userHandle: 'BASE64URL_USER', signCount: 0, isResidentCredential: true } }); await page.goto('https://rp.example.test/webauthn-login'); // Continue test flow... await browser.close(); })();
Operational guidance:
- Do not use virtual authenticators in production. They bypass UP/UV, which is the security guarantee of passkeys.
- For real agent operations, rely on delegated sessions minted by a human or by a backend OAuth client you control.
2FA channels: TOTP, SMS, email OTP, push
Agents should not "own" 2FA factors. If your property requires 2FA, coordinate a channel that supports automation in a controlled way without weakening overall security.
Recommended patterns:
- Prefer TOTP for service accounts when possible
- Create a dedicated automation account with TOTP, stored in a hardware or HSM‑backed secret manager. Rotate and audit.
- Brokered OTP retrieval for accounts you own
- For SMS/email, route inbound OTPs to a Secure Auth Broker service via provider APIs, not by scraping mailboxes.
- Enforce origin binding: only OTPs from whitelisted sender IDs and matching ongoing auth transactions are forwarded.
- Push‑based 2FA with explicit user consent
- Let a human approve a login request on their device; the SAB then mints a short‑lived session for the agent.
Example: minimal SMS OTP inlet (Twilio) for first‑party staging only:
js// Express.js endpoint that Twilio calls on inbound SMS. Store no OTP beyond short TTL. import express from 'express'; const app = express(); app.use(express.urlencoded({ extended: false })); // In-memory short TTL store keyed by transactionId type Pending = { code: string, expiresAt: number }; const pending = new Map<string, Pending>(); app.post('/otp/sms/inlet', (req, res) => { const body = req.body.Body || ''; const from = req.body.From || ''; // Simple extraction; in production use robust parsing and origin checks const match = body.match(/\b(\d{6})\b/); if (match) { const code = match[1]; // Associate with a transaction using metadata you control (e.g., include txn ID in RP SMS template in staging) const txnId = req.body.To; // placeholder; design this explicitly pending.set(txnId, { code, expiresAt: Date.now() + 2 * 60 * 1000 }); } res.type('text/xml').send('<Response></Response>'); }); app.get('/otp/sms/poll', (req, res) => { const txnId = String(req.query.txnId || ''); const p = pending.get(txnId); if (!p || p.expiresAt < Date.now()) return res.status(404).json({}); res.json({ code: p.code }); }); app.listen(8080);
Important:
- Only for accounts and environments you control. Respect the RP’s ToS.
- Implement strict origin validation (sender IDs, message templates, and correlation IDs) to avoid OTP leakage.
- Never store OTPs longer than necessary; encrypt in transit; audit access.
Device‑bound cookies and session lift/refresh
The industry is exploring device‑bound sessions to mitigate cookie theft (e.g., Device Bound Session Credentials). Meanwhile, many RPs already bind sessions to device signals. Agents need to plan for this.
Strategies:
- Prefer first‑party "session lift" for properties you own
- A human session in a normal browser uses a first‑party endpoint to mint an agent session bound to the agent’s user‑agent and origin. Scope it narrowly and short‑lived.
- Use OAuth/OIDC where available
- Refresh tokens with rotation, PoP (DPoP/MTLS) binding, and token introspection give better control than raw cookies.
- Persist state per agent identity
- Use separate user data directories per agent persona to keep device characteristics stable.
Playwright example: persist storage state for an agent identity
tsimport { chromium, BrowserContext } from '@playwright/test'; async function getContextFor(identity: string): Promise<BrowserContext> { const browser = await chromium.launch(); const context = await browser.newContext({ storageState: `./state.${identity}.json`, userAgent: `MyAgent/1.0 (${identity})` }); return context; } (async () => { const ctx = await getContextFor('sales-bot-1'); const page = await ctx.newPage(); await page.goto('https://rp.example.com/app'); // If first run requires login, do it once (ideally via SAB session lift), then persist await ctx.storageState({ path: './state.sales-bot-1.json' }); })();
Session refresh:
- Detect 401/403 and intercept redirect‑to‑login; pause agent actions.
- Ask the SAB for a fresh capability (token/cookie). Replace storage state atomically.
- Avoid storing refresh tokens inside the agent. Let the SAB be the only holder of long‑lived tokens.
Anti‑bot challenges: align, don’t evade
Anti‑bot providers (reCAPTCHA Enterprise, hCaptcha Enterprise, Cloudflare Turnstile, etc.) are there to protect your users and your infrastructure. Do not try to defeat them. Instead:
- Register your automation use case with the provider (for properties you own)
- Many providers support lower‑friction modes for known traffic (IP ranges, service accounts, server‑side assessments).
- Use server‑side risk assessment APIs when supported
- Let the SAB call an assessment endpoint to get a token that the agent later presents to the RP, instead of solving interactive challenges.
- Apply Privacy Sandbox primitives where applicable
- Private State Tokens allow first parties to convey trust signals across navigations without tracking users. Consider them if you operate both sides.
- Human‑in‑the‑loop fallback
- If a challenge is presented, route the event to a human approver in your org; on approval, mint a session lift for the agent. This preserves security guarantees and respects ToS.
What not to do:
- Don’t use third‑party CAPTCHA “solvers” on sites you don’t control.
- Don’t instrument headless browsers to mimic human micro‑movements specifically to evade detections. It’s brittle and often a ToS violation.
Deterministic replay without credential exposure
For agent development and CI, you want reproducible flows. For production, you want zero credential exposure. You can have both with environment scoping:
- Staging/CI environments
- Use virtual authenticators, synthetic identities, and seeded data.
- Record/replay deterministic network interactions (e.g., HAR) with sensitive headers redacted.
- Inject OTPs from a synthetic inlet; never from real inboxes.
- Production
- No HAR of auth endpoints; no raw tokens in logs.
- Agent never holds long‑lived credentials; it requests short‑lived capabilities from the SAB on demand.
Playwright HAR with redaction (staging only):
tsimport { chromium } from '@playwright/test'; (async () => { const browser = await chromium.launch(); const context = await browser.newContext(); // Redact sensitive headers before saving HAR await context.route('**/*', async (route) => { const headers = await route.request().allHeaders(); const scrubbed = { ...headers }; delete scrubbed['authorization']; delete scrubbed['cookie']; await route.continue({ headers: scrubbed }); }); await context.tracing.start({ screenshots: true, snapshots: true }); const page = await context.newPage(); await page.goto('https://rp.example.test/login'); // Perform deterministic login using virtual authenticator... await context.tracing.stop({ path: 'trace.zip' }); await context.close(); })();
Additionally, represent login steps at the capability level in your test specs: “Obtain agent session for user X” instead of “type password, click passkey button.” This avoids baking brittle UI steps into tests and mirrors production design.
Putting it together: an end‑to‑end flow
Goal: An agent needs to access a first‑party dashboard protected by FedCM + passkey + possible OTP step‑up, with anti‑bot protections enabled.
- One‑time human bootstrap
- Human logs into the dashboard on their normal browser via FedCM and passkey.
- They navigate to “Authorize Agent,” which initiates a back‑channel call to the SAB.
- SAB verifies their session, performs a server‑side risk assessment, and mints a short‑lived agent session cookie scoped to the agent’s UA and IP range. Optionally binds via PoP.
- Agent execution
- Orchestrator starts a job and requests a capability from SAB for identity “user‑123.”
- SAB returns a capability (cookie/token) with 15–30 minute TTL.
- Agent browser context is launched with a stable user data dir, applies the capability, and navigates to the dashboard.
- If the RP triggers a step‑up (e.g., 2FA due to anomaly), the page redirects; the agent pauses, emits an event to SAB.
- SAB either routes approval to the human (push) or uses a pre‑approved TOTP for the service account, then re‑mints the capability.
- Agent resumes.
- Refresh/rotation
- Five minutes before TTL expiry, agent asks SAB for refresh. SAB rotates token/cookie and invalidates the old one.
- Observability
- Metrics: login success rate, step‑up rate, token issuance latency, agent idle time during auth.
- Auditing: who authorized which agent, when, and for what scope.
Operational guardrails and hard‑won tips
- Keep environments cleanly separated
- Use separate domains and IdP tenants for staging vs prod. Never let test credentials leak into prod logs.
- Treat the agent’s user‑agent string as a first‑class identity
- Stable UA and stable device profile reduce anomaly flags. Don’t randomize UA unless required.
- Headless vs headed modes
- Some anti‑abuse layers penalize headless signatures. For first‑party automation, prefer headed mode in a secure enclave or a real desktop VM.
- Don’t conflate identity
- Use dedicated automation accounts with least privilege. Avoid sharing human accounts with agents.
- “Clicking” login UIs is a last resort
- If you own the RP, build a proper session lift or token grant. It’s more robust and secure than DOM automation.
What to do when you don’t own the RP
Sometimes your agent must log into a third‑party site.
- Prefer documented APIs or OAuth device flow if offered.
- If only browser login exists and ToS permits automation:
- Keep a human‑in‑the‑loop: someone completes FedCM/passkey steps; the agent takes over post‑login.
- Persist session state per agent identity; handle refresh by re‑prompting the human when needed.
- If anti‑bot blocks the agent, stop. Engage the provider for an enterprise arrangement or redesign the workflow.
Compliance, privacy, and logs
- Apply data minimization: capture only what’s necessary to troubleshoot.
- Encrypt traces at rest; restrict access via RBAC.
- Redact tokens/cookies in any trace or network logs. Build lint checks that fail CI if sensitive headers are present.
- Document ToS posture for every RP your agent touches.
Checklist: building a 2FA‑safe sign‑in pipeline
- Architecture
- Secure Auth Broker separated from agent runtime
- Capability tokens instead of sharing raw credentials
- Storage state isolated per agent identity
- FedCM
- Use session lift for first‑party; CDP FedCm only in staging
- Avoid DOM automation of FedCM in production
- Passkeys/WebAuthn
- Human UP/UV with delegated session for production
- Virtual authenticator only in CI
- 2FA
- Prefer TOTP for automation accounts
- OTP inlet with strict origin binding and TTL
- Anti‑bot
- Register automation use case; use server‑side assessments
- Human fallback for interactive challenges
- Sessions
- Refresh via SAB; agent holds only short‑lived capability
- Device stability (UA, storage), rotation policies
- Observability
- Metrics on auth outcomes; redacted traces
- Audit logs of who authorized what and when
References and further reading
- FedCM specification (WICG): https://wicg.github.io/FedCM/
- Chrome status on FedCM: https://chromestatus.com/feature/5739734405267456
- DevTools Protocol FedCm domain (testing): https://chromedevtools.github.io/devtools-protocol/tot/FedCm/
- WebAuthn Level 3 (W3C): https://www.w3.org/TR/webauthn-3/
- Passkeys developer guides: https://passkeys.dev/
- DevTools Protocol WebAuthn domain: https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/
- Private State Tokens (Privacy Sandbox): https://developer.chrome.com/docs/privacy-sandbox/private-state-tokens/
- Device Bound Session Credentials (background/proposal): https://developer.chrome.com/blog/device-bound-session-credentials/
- OAuth 2.0 DPoP (IETF): https://datatracker.ietf.org/doc/draft-ietf-oauth-dpop/
- reCAPTCHA Enterprise assessment API: https://cloud.google.com/recaptcha-enterprise/docs/assessments
- Cloudflare Turnstile: https://developers.cloudflare.com/turnstile/
Closing perspective
The right way to let agents sign in is to treat authentication as a capability grant, not a sequence of clicks. Use FedCM in the user’s browser, not the agent’s. Use passkeys with genuine user presence, then delegate narrowly scoped sessions. For OTPs, route them through a broker with strict correlation, not through scraped inboxes. And for anti‑bot controls, work with the platform: pre‑register, assess, and fall back to a human when required.
Do this, and you’ll end up with a login pipeline that’s more reliable, faster to maintain, and—most importantly—keeps users and data safe while giving your auto‑agents the access they legitimately need.
