> ## Documentation Index
> Fetch the complete documentation index at: https://docs.browserbase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Website authentication

> Managing 2FA and other authentication flows.

Many websites require authentication before allowing access to protected content or actions. Browserbase provides flexible methods for handling authentication in automated sessions, ensuring seamless logins while maintaining security.

**Why authentication matters in automation**

* Ensures access to restricted content without manual intervention.
* Reduces session expiration issues by persisting login states.
* Maintains consistent browser identity across sessions.

## Strategies for handling authentication

Handling authentication in automation requires **maintaining session state and resolving challenges like CAPTCHAs or multi-factor authentication (MFA)**. Browserbase provides several strategies to help you authenticate reliably while ensuring security and efficiency.

1. Create a session with context, proxies, and fingerprinting.
2. Use the Session Live View to log into the website.
3. Use the context ID to persist the authentication state across future sessions.

### Create a session with contexts, proxies, and fingerprinting

Ensure seamless authentication by persisting login sessions and preventing IP-based blocking.

* [Apply Contexts](/platform/browser/core-features/contexts) → Store cookies, session tokens, and local storage to prevent repeated logins. Log in once, then reuse the saved authentication state.
* [Enable Verified](/platform/identity/overview) → Use consistent browser fingerprints recognized by bot protection partners.
* [Use Proxies](/platform/identity/proxies) → Rotate residential proxies and match IP locations to prevent tracking and login restrictions.

By combining contexts, Verified, and proxies, you can create secure, stable, and automated authentication workflows.

<Tabs>
  <Tab title="Node.js">
    <CodeGroup>
      ```typescript SDK theme={null}
      import { Browserbase } from "@browserbasehq/sdk";

      async function createAuthSession(contextId: string) {
        const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });

        const session = await bb.sessions.create({
          browserSettings: {
            context: {
              id: contextId,
              persist: true
            }
          },
          proxies: [{
            type: "browserbase",
            geolocation: {
              city: "New York",
              state: "NY",
              country: "US"
            }
          }]
        });

        console.log("Session URL: https://browserbase.com/sessions/" + session.id);
        return session;
      }

      // Use the context ID from your saved context
      const contextId = "<context-id>";
      const session = await createAuthSession(contextId);
      console.log("Session URL: https://browserbase.com/sessions/" + session.id);
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Python">
    <CodeGroup>
      ```python SDK theme={null}
      from browserbase import Browserbase
      import os

      bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])

      def create_auth_session(context_id: str):
        session = bb.sessions.create(
          browser_settings={
            "context": {
              "id": context_id,
              "persist": True
            }
          },
          proxies=[{
            "type": "browserbase",
            "geolocation": {
              "city": "New York",
              "state": "NY",
              "country": "US"
            }
          }]
        )
        print("Session URL: https://browserbase.com/sessions/" + session.id)
        return session

      # Use the context ID from your saved context
      context_id = "<context-id>"
      session = create_auth_session(context_id)
      ```
    </CodeGroup>
  </Tab>
</Tabs>

### Use the session live view to log in

For authentication workflows, the best practice is to **log in manually once using Session Live View**, then **persist the authentication state across future sessions using contexts**. This approach ensures secure, repeatable logins without needing manual input every time.

1. Start a new session and retrieve the **Session Live View URL**.
2. Open the Live View in your browser to **interact with the session in real time**.
3. Once logged in, the session's authentication data (cookies, session tokens) is stored.
4. Save the session context id so future sessions can **reuse the authentication state without logging in again**.

<Card title="Taking a session's remote control with Session Live View" href="/platform/browser/observability/session-live-view">
  Incorporate a human in the loop to complete the authentication process.
</Card>

### Use the context ID to persist the authentication state across future sessions

After logging in once, you can reuse the authentication state by storing it in a context. This allows future sessions to bypass the login process, maintaining access to authenticated pages without needing manual input.

Now, any session using this context.id will start already logged in, eliminating the need to authenticate again. By persisting authentication with contexts, you can ensure seamless automation, reduce login failures, and improve session continuity.

## 2FA challenges

Two-step verification (via authenticator apps or SMS) or magic links usually require human intervention in the loop. There are 2 main strategies to manage 2FA:

1. Disable 2FA or create an app password
2. Enable Remote Control of your Session

### Disable 2FA or create an app password

For an internal tool, try to turn off the
[two-step verification](https://support.google.com/accounts/answer/1064203?hl=en\&co=GENIE.Platform%3DDesktop).

For an authentication flow requiring some level of security,
[try to create an app password](https://support.google.com/accounts/answer/185833?hl=en).

### Enable remote control of your session

If a two-step verification mechanism can't be bypassed or disabled, consider handing back control to the end user with Session Live URLs.

<Card title="Taking a session's remote control with Session Live View" href="/platform/browser/observability/session-live-view">
  Let your end users complete the two-step verification process as part of your
  automation.
</Card>

## Accessing an authentication flow with Verified

Many auth flows try to block automation:

* IP address restrictions
* User agent filtering
* CAPTCHAs
* Rate limiting

Browserbase handles these for you:

* [Proxies](/platform/identity/proxies#use-built-in-proxies) for consistent geolocation and network identity
* [Verified](/platform/identity/overview) sessions recognized by bot protection partners

## Speed up your automation by reusing cookies

Some websites or web apps rely on cookie-based sessions, which can be easily retrieved and reused to speed up your automation.

The code examples below showcase how to retrieve and set cookies to avoid having your automation go through the authentication flow at each run:

<Tabs>
  <Tab title="Node.js">
    ```typescript Playwright theme={null}
    import Browserbase from "@browserbasehq/sdk";
    import { chromium } from "playwright-core";
    import storage from "./storage.js";

    async function authenticate(page, context) {
      const session = await storage.getSession();
      if (session) {
        await context.addCookies([session]);

        // try to access a protected page
        await page.goto("https://www.browserbase.com/overview");

        if (page.url === "https://www.browserbase.com/overview") {
          // no redirect -> already authenticated, skip the auth flow
          return;
        }
      }

      await page.goto("https://www.browserbase.com/sign-in");

      // ... sign-in ...

      // retrieve User Session Cookie
      const cookies = await context.cookies();
      const sessionCookie = cookies.find((c) => c.name === "session_id");
      await storage.storeSession(sessionCookie);
    }

    (async () => {
      const bb = new Browserbase({
        apiKey: process.env.BROWSERBASE_API_KEY
      });
      const session = await bb.sessions.create({
          proxies: true
      });
      const browser = await chromium.connectOverCDP(session.connectUrl);

      // Getting the default context to ensure the sessions are recorded.
      const defaultContext = browser.contexts()[0];
      const page = defaultContext.pages()[0];

      await authenticate(page, defaultContext);

      // ... interact with page ...

      await page.close();
      await browser.close();
    })().catch((error) => console.error(error.message));
    ```
  </Tab>

  <Tab title="Python">
    ```py Playwright theme={null}
    # The first time this file is run, the authentication cookies will be stored
    # to a file. Subsequent runs will load those cookies from the file.
    import json
    import os
    from browserbase import Browserbase
    from playwright.sync_api import sync_playwright, Page

    API_KEY = os.environ["BROWSERBASE_API_KEY"]


    SITE_URL = "https://practice.expandtesting.com"
    SITE_LOGIN_URL = f"{SITE_URL}/login"
    SITE_PROTECTED_URL = f"{SITE_URL}/secure"

    # Credentials to use for testing the login flow.
    # For testing only! Don't store secrets in code.
    SITE_USERNAME = "practice"
    SITE_PASSWORD = "SuperSecretPassword!"

    # This would typically be stored in some other durable storage or even kept in
    # memory. Here, we're just going to serialize them to disk using json dump/load.
    # Ensure these are well secured as anyone with this information can log in!
    COOKIE_FILE = "test-cookies.json"


    def store_cookies(browser_tab: Page):
        # Retrieve all the cookies for this URL
        all_cookies = browser_tab.context.cookies(SITE_URL)

        # You might want to put these in some durable storage, but for now
        # just keep them in a simple file as JSON.
        with open(COOKIE_FILE, "w") as cookie_file:
            json.dump(all_cookies, cookie_file, indent=4)

        print(f"Saved {len(all_cookies)} cookie(s) from the browser context")


    def restore_cookies(browser_tab: Page):
        # Return all cookies to the browser context

        try:
            with open(COOKIE_FILE) as cookie_file:
                cookies = json.load(cookie_file)
        except FileNotFoundError:
            # No cookies to restore
            return

        browser_tab.context.add_cookies(cookies)
        print(f"Restored {len(cookies)} cookie(s) to the browser context")


    def authenticate(browser_tab: Page):
        # Navigate to the sign-in page, enter the site credentials and sign in
        print("Attempting to log in")
        browser_tab.goto(SITE_LOGIN_URL)

        browser_tab.get_by_role("textbox", name="username").fill(SITE_USERNAME)
        browser_tab.get_by_role("textbox", name="password").fill(SITE_PASSWORD)
        browser_tab.get_by_role("button", name="Login").click()

        # Store the site cookies
        store_cookies(browser_tab)


    def run(browser_tab: Page):
        # Load up any stored cookies
        restore_cookies(browser_tab)

        # Instruct the browser to go to a protected page
        browser_tab.goto(SITE_PROTECTED_URL)

        if browser_tab.url != SITE_PROTECTED_URL:
            # Redirected, almost certainly need to log in.
            authenticate(browser_tab)

            # Try again
            browser_tab.goto(SITE_PROTECTED_URL)

        # Print out a bit of info about the page it landed on
        print(f"{browser_tab.url=} | {browser_tab.title()=}")

        ...


    with sync_playwright() as playwright:
        bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
        # A session is created on the fly
        session = bb.sessions.create(
            proxies=True
        )
        browser = playwright.chromium.connect_over_cdp(session.connectUrl)

        # Print a bit of info about the browser we've connected to
        print(
            "Connected to Browserbase.",
            f"{browser.browser_type.name} version {browser.version}",
        )

        context = browser.contexts[0]
        browser_tab = context.pages[0]

        try:
            # Perform browser commands
            run(browser_tab)

        finally:
            # Clean up
            browser_tab.close()
            browser.close()

    ```
  </Tab>
</Tabs>

<Note>
  **Using Functions?** When deploying authentication flows with [Functions](/platform/runtime/overview), you can combine context persistence with serverless execution. Define your function with a `contextId` in the session configuration to maintain authenticated state across function invocations.
</Note>

## Handling passkeys

[Passkeys](https://www.w3.org/TR/webauthn-3/) are a modern authentication method that can present challenges for automation since they typically require user interaction. When automating sites that use passkeys, you'll often want to disable or bypass them since the required user interactions aren't supported in automated sessions.

### Disable passkeys in your session

To prevent passkey prompts from appearing and potentially blocking your automation, you can disable them using the Chrome DevTools Protocol (CDP). Here's how:

<Tabs>
  <Tab title="Node.js">
    ```typescript theme={null}
    import { chromium } from "playwright-core";
    import { Browserbase } from "@browserbasehq/sdk";

    async function createSessionWithoutPasskeys() {
      const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
      const session = await bb.sessions.create();
      console.log(`Session created, id: ${session.id}`);

      // Connect to the remote browser
      const browser = await chromium.connectOverCDP(session.connectUrl);
      const defaultContext = browser.contexts()[0];
      const page = defaultContext.pages()[0];

      // Create a CDP session and configure the virtual authenticator
      const client = await page.context().newCDPSession(page);
      await client.send('WebAuthn.enable');
      await client.send('WebAuthn.addVirtualAuthenticator', {
        options: {
          protocol: 'ctap2',
          transport: 'internal',
          hasResidentKey: true,
          hasUserVerification: true,
          isUserVerified: true,
          automaticPresenceSimulation: true,
        },
      });

      return page;
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from browserbase import Browserbase
    from playwright.sync_api import sync_playwright
    import os

    def create_session_without_passkeys():
        bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
        session = bb.sessions.create()
        print(f"Session created, id: {session.id}")

        # Connect to the remote browser
        with sync_playwright() as playwright:
            browser = playwright.chromium.connect_over_cdp(session.connect_url)
            context = browser.contexts[0]
            page = context.pages[0]

            # Create a CDP session and configure the virtual authenticator
            client = page.context.new_cdp_session(page)
            client.send("WebAuthn.enable")
            client.send("WebAuthn.addVirtualAuthenticator", {
                "options": {
                    "protocol": "ctap2",
                    "transport": "internal",
                    "hasResidentKey": True,
                    "hasUserVerification": True,
                    "isUserVerified": True,
                    "automaticPresenceSimulation": True,
                }
            })

            return page
    ```
  </Tab>
</Tabs>

This code:

1. Creates a new Browserbase session
2. Connects to the browser using CDP
3. Enables the WebAuthn API
4. Adds a virtual authenticator that prevents real passkey prompts

By setting up this virtual authenticator, you prevent the browser from prompting for actual passkey authentication, allowing your automation to proceed with other authentication methods like username/password.

### Alternative authentication methods

When passkeys are enabled on a site, there's usually an alternative authentication method available (like username/password). After disabling passkeys, look for these alternative methods:

* "Sign in with password" links
* "Other sign-in options" buttons
* Username/password form toggles
