DEV Community

Isaias Velasquez
Isaias Velasquez

Posted on

[BA-007] Logins, sessions and authentication in browser automation

Many automation tasks require being logged in. The naive approach is to fill the login form every time, but that is slow and breaks if the page changes.

Playwright lets you save and restore browser sessions using storage_state:

from playwright.sync_api import sync_playwright

def save_session(url: str, username: str, password: str, save_path: str):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        page.goto(url)

        page.fill("#username", username)
        page.fill("#password", password)
        page.click("#login-button")

        page.wait_for_url("**/dashboard")

        context = browser.contexts[0]
        context.storage_state(path=save_path)
        print(f"Session saved to {save_path}")

        browser.close()

save_session(
    "https://example.com/login",
    "your-username",
    "your-password",
    "session.json"
)
Enter fullscreen mode Exit fullscreen mode

Once you have the session file, you can reuse it without logging in again:

from playwright.sync_api import sync_playwright

def load_session(start_url: str, session_path: str):
    with sync_playwright() as p:
        context = p.chromium.launch_persistent_context(
            user_data_dir="./profile",
            storage_state=session_path
        )
        page = context.new_page()
        page.goto(start_url)

        if "login" not in page.url:
            print("Session restored, already logged in")
        else:
            print("Session expired, need to login again")

        browser = context.browser
        browser.close()
Enter fullscreen mode Exit fullscreen mode

The storage_state file contains cookies and localStorage data. It works across browser launches.

For sites with 2FA, automate the login once manually and save the session. Then reuse it for all subsequent runs:

def login_with_2fa_once(save_path: str):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        page.goto("https://github.com/login")

        page.fill("#login_field", "your-email")
        page.fill("#password", "your-password")
        page.click("[name=commit]")

        input("Enter the 2FA code and press Enter here after logging in...")

        context = browser.contexts[0]
        context.storage_state(path=save_path)
        print(f"Session with 2FA saved to {save_path}")
        browser.close()
Enter fullscreen mode Exit fullscreen mode

This pauses and waits for you to enter the 2FA code manually. After that the session is saved and can be reused.

You can also handle session expiration by checking the current URL and retrying the login:

def with_session(url: str, session_path: str, login_fn):
    with sync_playwright() as p:
        context = p.chromium.launch_persistent_context(
            user_data_dir="./profile",
            storage_state=session_path
        )
        page = context.new_page()
        page.goto(url)

        if "login" in page.url:
            print("Session expired, logging in again")
            login_fn(page)
            context.storage_state(path=session_path)

        return page
Enter fullscreen mode Exit fullscreen mode

This pattern checks if the page redirected to a login page and refreshes the session automatically.

Using saved sessions turns a multi step login into a one second page load.

That's all for now.
Thanks for reading!

Top comments (1)

Collapse
 
double_chen_70da460344c73 profile image
Double CHEN

Solid writeup. The storage_state approach works great until you hit sites with aggressive cookie expiry or 2FA that rotates every session. I ran a project scraping 30+ sites daily and writing login logic per site became a maintenance nightmare. Switched to browser-act CLI which handles session persistence natively -- just open the browser once in headed mode, log in manually, and subsequent runs reuse the session without any storage_state boilerplate. The browser open command with a session name is all you need. Saved me from maintaining login scripts for each target.