Sessions & Auth

Login & Credentials

Login is the step automation gets stuck on most often. This page lays out three reliable paths: drive the login form directly with snapshot → fill → click → wait, tuck accounts into the chrome-use auth credential vault for one-command replay, and handle Sign in with Google / OAuth plus Bitwarden passwords and passkeys. The goal is to have the agent complete login in a real browser without leaving passwords in your shell history.

The basic login flow

The most direct approach: open the login page, use snapshot -i to read the interactive elements in the form, pick out the @refs for the email, password, and submit button, fill them one by one, click, and finally use wait --url to confirm you've landed on the post-login page.

Form login
$ chrome-use open https://app.example.com/login
$ chrome-use snapshot -i

# Pick the @ref for the email / password / submit button out of the snapshot, then:
$ chrome-use fill @e3 "user@example.com"
$ chrome-use fill @e4 "hunter2"
$ chrome-use click @e5
$ chrome-use wait --url "**/dashboard"
$ chrome-use snapshot -i
ℹ️ Why snapshot first, every time
A @ref is a handle to an element in the current snapshot — it can go stale the moment the page changes. Get into the habit of running snapshot -i to grab the current refs before acting; it's far more stable than hardcoding selectors. The final snapshot -i is there to confirm login actually succeeded (and you didn't stall on a login page showing an error).
⚠️ Plaintext passwords leak
Writing a password straight into a command leaves it in your shell history, process list, and logs. Only do this for throwaway local debugging; any sensitive account should switch to the credential vault or Bitwarden below, so the password never touches the command line.

Credential vault (auth save / login)

For sites you log into over and over, store the credential in the local vault once and then replay it with a single command that auto-fills and submits. --password-stdin reads the password from standard input, keeping it out of shell history.

auth save + auth login
# Save a credential (password read from stdin — press Ctrl+D when done)
$ chrome-use auth save my-app --url https://app.example.com/login \
    --username user@example.com --password-stdin

# From then on, one command: open the login page, fill, click, wait for the form to finish
$ chrome-use auth login my-app
✅ How the vault and Bitwarden divide the work
auth save tucks credentials into chrome-use's local vault; the bwu below instead pulls them from Bitwarden. The two work together: keep the password in Bitwarden and fetch it just-in-time at login, so the credential never lands on the command line.

Sign in with Google / OAuth

Click the site's own Google login button, then snapshot -i: even when the GSI "Continue as <user>" button sits inside a cross-origin iframe, you'll still get a clickable @ref (Chrome 125+). If a account-picker popup opens instead, just switch to that popup and drive it — the relay follows the popup your click opened.

OAuth redirect flow
# Kick off the OAuth flow
$ chrome-use open https://app.example.com/auth/google

# Wait for the redirect to Google, then read a fresh snapshot
$ chrome-use wait --url "**/accounts.google.com**"
$ chrome-use snapshot -i

# Fill in the Google account, step by step
$ chrome-use fill @e1 "user@gmail.com"
$ chrome-use click @e2
$ chrome-use wait 2000
$ chrome-use snapshot -i
$ chrome-use fill @e3 "password"
$ chrome-use click @e4

# Wait for the redirect back to the app site
$ chrome-use wait --url "**/app.example.com**"
ℹ️ Reusing an already-logged-in browser is easiest
If your local Chrome is already signed into Google / the target site, rather than re-running the whole OAuth flow, just drive that real Chrome directly, or save its session state for reuse — see Sessions & persistence.

Sign in with Bitwarden passwords and passkeys

On the login page, run auth login --bwu to select the site's account through bitwarden-use (0.7.0+). Among several matching accounts it uses the one that signed in to that host last time (recorded by id only in ~/.chrome-use/bwu-items.json), else the one named exactly the site's host or the host of its sign-in frame (App Store Connect signs in through an idmsa.apple.com iframe), and says which and why on stderr; otherwise use --item. A site adapter that finds the site signed out reports a login wall: too (see site adapters). The first login wall on a site asks whether to sign in from the vault this time, always for that site, or never; an agent gets the question in loginWall.ask and must relay it to you. auth autologin status lists the decisions (stored per site in ~/.chrome-use/autologin.json), auth autologin always|never|off <host> changes one. Passwords, TOTP and private keys are passed through bwu run to the child, never placed in arguments or output.

Some QR sign-ins ask for a second, security-verification scan after the phone confirmed the first. Xiaohongshu shows a "Security Verification" (安全验证) dialog with a new QR code over the page (it expires in about a minute), its QR status poll answers HTTP 471, and the sign-in box underneath still says to confirm on the phone. Every command then warns login verification: … and carries loginVerification in --json (the code is in qrSrc): give that QR code (screenshot or qrSrc) to the user to scan with the signed-in app, and do not reload, open another page or sign in again until they have, or the verification is lost. Auto-login and site login walls leave the page alone meanwhile.

In a --launch browser, add --passkey to sign in using only the vault passkey (bwu 0.9.0+), without reading passwords, TOTP or custom fields. Ordinary password login can also answer a passkey second factor. Credentials go into a temporary WebAuthn authenticator on the tab and Chrome signs the request; the authenticator is removed after the attempt. While it is installed, navigator.credentials.create() is refused in the current page, its existing same-process and attached cross-process iframes, and every new document; afterwards each is restored, and any guard left in place is an error. Known limits: page script that ran before the guard (the page already open when the login starts) can have kept the original function or replaced Proxy and bypass it, and a cross-process iframe that attaches after the install is not guarded. In both cases the registration is still detected and aborts the command; unexpected registration or unconfirmed cleanup aborts the command. Only synced passkeys with counter 0 are supported; nonzero counters need vault write-back and are refused. Passkeys are unsupported on the extension relay: passkey-only login fails immediately; ordinary login keeps the password/TOTP flow. Without WebAuthn support, ordinary login falls back to passwords; passkey-only login fails.

auth login --bwu
$ curl -fsSL https://raw.githubusercontent.com/leeguooooo/bitwarden-use/main/install.sh | sh
$ chrome-use open https://github.com/login
$ chrome-use auth login --bwu --item github.com
$ chrome-use --session passkey-demo --launch open https://github.com/login
$ chrome-use --session passkey-demo --launch auth login --bwu --item github.com --passkey
$ chrome-use --session passkey-demo --launch snapshot -i

--no-submit fills only, skips TOTP and installs no passkey authenticator; it cannot be combined with --passkey. A signed assertion does not prove site acceptance: check the authenticated destination with snapshot or the site's authenticated state.

A password login is reported only when the site took it: after submitting, it waits until the sign-in form is gone from a loaded page (read twice in a row) and returns signedIn: true with the evidence; when the form is still there after about 12 s it fails with the sign-in was not confirmed, quoting the page's own message. The Enter is checked too: on a background tab of your own Chrome a key can be accepted by Chrome and never reach the page, so nothing was submitted, and the form's sign-in button is clicked instead (submittedWith says so).


Handling two-factor authentication (2FA)

When you hit a second step that needs SMS / TOTP / a hardware key, the least painful approach is to let a human do that step while the script waits alongside for the redirect. On the default extension path driving your real Chrome, the window is already visible, so just do the second step yourself; hand control back formally with session handoff (see Sessions & Persistence) — after handoff the agent refuses driving commands on that session until you return it. --headed is only needed when you --launch a separate browser, paired with a longer --timeout to wait for the post-login URL.

2FA: human completes the second step
# Show the browser window, log in with credentials first
$ chrome-use open https://app.example.com/login --headed
$ chrome-use snapshot -i
$ chrome-use fill @e1 "user@example.com"
$ chrome-use fill @e2 "password123"
$ chrome-use click @e3

# Complete 2FA by hand in the window; the script waits for the redirect (up to 120s)
$ chrome-use wait --url "**/dashboard" --timeout 120000

HTTP Basic and cookie auth

Not every site uses a form. For HTTP Basic Auth, set the credentials before navigating; when you need to inject a session manually, you can set the cookie directly.

Basic Auth / cookie injection
# HTTP Basic: set username / password before navigating
$ chrome-use set credentials username password
$ chrome-use open https://protected.example.com/api

# Set an auth cookie manually
$ chrome-use cookies set session_token "abc123xyz"
$ chrome-use open https://app.example.com/dashboard

Log in once, skip it afterward

Login is expensive — don't redo it every time. After a successful login, save the cookies and localStorage to a state file, then load it later with --state to start up already "logged in." It's the most practical trick for skipping the whole OAuth / 2FA dance.

state save / --state reuse
# Log in once, save cookies + localStorage
$ chrome-use state save ./auth.json

# Later runs start up already logged in
$ chrome-use --state ./auth.json open https://app.example.com
ℹ️ Want automatic save / restore?
Use --session-name (or AGENT_BROWSER_SESSION_NAME) to automatically save and load a session by name, with no files to manage by hand. State files contain plaintext session tokens — the details on encryption, gitignore, and named sessions are all in Sessions & persistence.

Security notes

Login inevitably touches credentials and session tokens. A few baselines:

⚠️ A state file = a reusable key
A saved auth state is equivalent to a live login session — anyone who gets it can impersonate you. Treat it like a password: least privilege, encrypted, destroyed promptly. Don't use shared directories, and don't dump it into logs.