Back to Blog
Python

Python Playwright: Browser Context, Cookies, and Authentication

python playwright browser context cookies and authentication: Learn how to manage cookies and authentication in Playwright browser contexts, including storage_state re...

Playwrightbrowser contextcookiesauthenticationweb scrapingtest automation
Illustration of a browser context with cookie jar and authentication key, symbolizing session state management in Playwright.

When automating a browser with Python Playwright, the browser context is the unit that holds cookies, local storage, and authentication state. Understanding how cookies and authentication interact with browser contexts is essential for building reliable login flows, scraping authenticated pages, and running tests that require a logged-in session. This article focuses on python playwright browser context cookies and authentication, showing how to persist, restore, and manage session state without repeating login steps.

Why Browser Contexts Matter for Authentication

Playwright's browser.new_context() creates an isolated browsing environment. Each context has its own cookie jar, localStorage, and cache, completely separate from other contexts. This isolation is the foundation for handling authentication: you can log in once in a context, save the resulting state, and reuse it later without performing the login again.

A common mistake is to treat the browser instance as the unit of state. In Playwright, the browser is a process that can host multiple contexts, each with distinct cookies and permissions. For example, you can run two contexts simultaneously, one authenticated as a regular user and another as an admin, without any interference. This design makes contexts the natural place to manage authentication.

The authentication state is not stored in the browser process itself but in the context. When you close a context, its cookies and storage are discarded unless you explicitly persist them. This behavior is both a feature and a trap: it keeps tests isolated but requires deliberate handling when you want to reuse a session.

How Cookies Are Stored in a Browser Context

Cookies set by a website are stored in the context's cookie jar. Playwright exposes this jar through methods like context.cookies() and context.add_cookies(). The cookie jar is per-context, so cookies from one context never leak into another.

When a page makes a request, Playwright automatically attaches the cookies that match the URL's domain and path, following standard browser rules. This means you don't need to manually attach cookies to each request; the context handles it transparently.

Here is a minimal example of inspecting cookies after a login:

from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context() page = context.new_page() page.goto('https://example.com/login') # perform login steps... cookies = context.cookies() print(cookies) browser.close()

The cookies() method returns a list of cookie objects, each containing name, value, domain, path, expires, httpOnly, secure, and sameSite. You can inspect these to understand what the site stores, but in practice you rarely need to read them directly. The more useful operation is saving the entire state.

Saving and Reusing Authentication State with storage_state

Playwright provides context.storage_state() to capture the current cookies and localStorage in a JSON-serializable object. This object can be written to a file and later passed to browser.new_context() to recreate the same authenticated session.

This is the standard way to avoid repeating login steps across test runs or scraping sessions. The flow is:

  1. Launch a browser, create a context, and perform the login.
  2. Call storage_state() and save the result to a file.
  3. In a later run, create a new context with storage_state loaded from that file.

Example of saving state after login:

from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context() page = context.new_page() page.goto('https://example.com/login') page.fill('#username', 'user') page.fill('#password', 'pass') page.click('#submit') page.wait_for_url('https://example.com/dashboard') # Save the authenticated state state = context.storage_state() with open('auth_state.json', 'w') as f: f.write(json.dumps(state)) browser.close()

Reusing the state in a new run:

import json from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() with open('auth_state.json') as f: state = json.load(f) context = browser.new_context(storage_state=state) page = context.new_page() page.goto('https://example.com/dashboard') # The page should load as the authenticated user browser.close()

The storage_state parameter accepts a file path or a dictionary. When you pass a dictionary, Playwright applies those cookies and localStorage to the context before any page loads. This is efficient because no login request is made.

Note that storage_state captures cookies and localStorage, but not sessionStorage. If your application relies on sessionStorage for authentication, this method will not preserve it. Most auth systems use cookies or localStorage, but you should verify your target site.

Adding and Managing Cookies Directly

Sometimes you already have the cookie values from an external source, such as an API response or a previous session. Playwright allows you to inject cookies directly into a context using context.add_cookies(). This is useful when you need to set a specific session token without performing a full login.

The add_cookies method expects a list of cookie objects with at least name, value, url or domain and path. Here is an example:

from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context() context.add_cookies([{ 'name': 'sessionid', 'value': 'abc123', 'domain': 'example.com', 'path': '/' }]) page = context.new_page() page.goto('https://example.com/') # The cookie is sent with the request browser.close()

When you add a cookie, you must provide either url or both domain and path. If you use url, Playwright derives the domain and path from it. The secure and httpOnly flags can be set as well, but they are not required for the cookie to be sent over HTTP. Be aware that if the site checks httpOnly on the client side, it won't matter because Playwright sends cookies automatically.

Direct cookie injection is a double-edged sword. It gives you precise control, but it bypasses the browser's natural login flow. If the site uses additional client-side state, such as a CSRF token in localStorage, you must set that separately. In most cases, using storage_state is safer because it captures the full state.

Handling Dynamic Authentication Flows

Not all authentication is a simple form submission. Many sites use multi-step flows, OAuth redirects, or JavaScript-based token refresh. Playwright can handle these because it runs a real browser, but the challenge is knowing when the login is complete.

A common pattern is to wait for a specific URL or element that only appears after authentication. For example, after clicking a login button, you might wait for the dashboard URL or a user avatar. The page.wait_for_url() method is useful for redirects.

For OAuth flows, you may need to interact with a third-party provider. The browser context can navigate to the provider, fill credentials, and then redirect back. Because the context persists cookies across navigations, the session token is stored automatically.

Here is an example of a login that waits for a network response or a specific element:

from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context() page = context.new_page() page.goto('https://example.com/login') page.fill('#username', 'user') page.fill('#password', 'pass') page.click('#submit') # Wait for the dashboard heading to appear page.wait_for_selector('.dashboard-header', timeout=10000) # Now the context has the auth cookies state = context.storage_state() browser.close()

If the login flow involves a redirect to an identity provider, you might need to switch to a popup or a new page. Playwright's context.expect_page() can capture a popup that opens during the flow. The key is to wait for the final authenticated page, not just the response of the login request.

Another dynamic aspect is token expiration. If you save a storage state and reuse it hours later, the session may have expired. Playwright does not refresh tokens automatically. You need to handle the case where the saved state is no longer valid, typically by detecting a redirect to the login page and re-authenticating.

Security and Operational Considerations for Stored Credentials

Storing authentication state in a JSON file is convenient, but it introduces security risks. The file contains session cookies, which are as sensitive as passwords. If an attacker gains access to the file, they can impersonate the user. Treat these files like credentials: store them in a secure location, restrict file permissions, and never commit them to version control.

When you generate a storage state file, consider the following:

  • Use a dedicated directory that is excluded from your repository (e.g., .gitignore).
  • Set file permissions so only the current user can read it (e.g., chmod 600 on Unix).
  • If you run automation in a CI/CD pipeline, use secret management to inject the state file at runtime rather than storing it in the repository.

Playwright itself does not encrypt the storage state. The JSON contains plain-text cookie values. If you need to store it in a database or a shared system, encrypt it with a key that is not stored alongside the data.

Another operational concern is the lifetime of the saved state. Sessions expire, and the expiration is controlled by the server, not by Playwright. You should design your automation to detect when a session is no longer valid. A simple check is to navigate to a protected page and see if it redirects to the login page. If it does, you can re-run the login flow and refresh the saved state.

For long-running scraping tasks, it is often better to re-authenticate periodically rather than relying on a single saved state. You can schedule a fresh login every few hours and overwrite the state file. This reduces the risk of hitting expired sessions in the middle of a large batch.

Common Pitfalls When Working with Contexts and Cookies

One frequent mistake is creating a new context without passing the storage state, expecting the cookies to persist from a previous context. Cookies are scoped to the context, not the browser. If you create a new context, you start with an empty cookie jar unless you explicitly provide storage_state or add_cookies.

Another pitfall is using storage_state with a file path that does not exist. Playwright will raise an error if the file is missing. Always check that the file exists before passing it, or catch the exception and fall back to a fresh login.

A subtle issue arises with cross-domain cookies. If your application uses cookies from multiple domains, storage_state captures all of them, but when you reuse the state, the cookies are applied only if the domain matches the pages you visit. If you navigate to a different domain, those cookies are not sent. This is correct browser behavior, but it can confuse developers who expect all cookies to be present everywhere.

Another common problem is mixing sync and async Playwright APIs. The examples above use the sync API, which is simpler for scripts. If you use the async API, the methods return coroutines and require await. The behavior is identical, but forgetting await can lead to missing cookies or state. Always be consistent with the API you choose.

Finally, be careful with context.cookies() when you have multiple pages open. Cookies are shared across all pages in the same context, so reading cookies after a login on one page reflects the state for the entire context. This is usually what you want, but if you have parallel actions that modify cookies, you may see race conditions. In such cases, use a separate context for each independent session.

Understanding how browser contexts, cookies, and authentication interact in Playwright is the key to building robust automation. By using storage_state to save and reuse sessions, you avoid redundant logins and make your scripts faster and more reliable. Direct cookie injection gives you fine-grained control when you need it, but always weigh the security implications of storing session data. With these techniques, you can handle both simple form logins and complex dynamic authentication flows in Python Playwright.

python playwright browser context cookies and authentication | RYUSLOG DEV