ScreenshotNeo

BlogHow-to

How to Access Login-Protected Django Views with Puppeteer

Log in through Django’s form, preserve the browser session, and verify protected pages with Puppeteer. Includes runnable code and fixes for common failures.

By the ScreenshotNeo team30 September 20268 min read

How to Access Login-Protected Django Views with Puppeteer

To access a login-protected Django view with Puppeteer, use the site’s normal login form, let the browser receive and retain Django’s session cookies, then navigate to the protected URL and check for a page-specific success condition. Puppeteer’s page.authenticate() is for HTTP authentication; it does not log a user into a typical Django form-based application.

The exact login URL, form selectors, redirect behavior, and authenticated-page marker depend on the application. The example below shows the sequence; replace its URLs and selectors with those used by your site. It uses Puppeteer’s locator API, so check the documentation for the version installed in your project. Puppeteer Page API

1. Install Puppeteer and prepare credentials

In a Node.js project, install Puppeteer:

npm install puppeteer

Keep the account credentials outside the source code. For local development, environment variables are convenient; in a deployed job, use the platform’s secret store. Do not print passwords, session IDs, CSRF tokens, or cookie values to logs.

export DJANGO_BASE_URL="https://your-django-site.example"
export DJANGO_USERNAME="your-automation-user"
export DJANGO_PASSWORD="load-this-from-a-secret-store"

Use an account authorized to access the target view. If the site requires MFA, an identity provider, CAPTCHA, or an approval step, those are application-specific parts of the login flow; the generic form example cannot bypass them.

2. Log in through the rendered Django form

A rendered login form is usually the most reliable automation path because the browser obtains the site’s cookies and submits the form’s own fields, including its CSRF token where present. Django’s session framework exposes session state through request.session; the browser typically carries a cookie identifying the session. Django authentication rotates the session key at login as a session-fixation mitigation. Django sessions documentation

The browser logs in through the application form, keeps the session, then requests the protected view.
The browser logs in through the application form, keeps the session, then requests the protected view.
const puppeteer = require('puppeteer');

async function main() {
  const baseUrl = process.env.DJANGO_BASE_URL;
  const username = process.env.DJANGO_USERNAME;
  const password = process.env.DJANGO_PASSWORD;

  if (!baseUrl || !username || !password) {
    throw new Error('Set DJANGO_BASE_URL, DJANGO_USERNAME, and DJANGO_PASSWORD');
  }

  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();

  try {
    // Load the real login page first so its cookies and form state are present.
    await page.goto(new URL('/accounts/login/', baseUrl), {
      waitUntil: 'domcontentloaded',
      timeout: 30_000,
    });

    // These selectors are examples. Inspect the application's actual form.
    await page.locator('input[name="username"]').fill(username);
    await page.locator('input[name="password"]').fill(password);

    // Start waiting before clicking so a fast navigation is not missed.
    await Promise.all([
      page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 30_000 }),
      page.locator('form button[type="submit"]').click(),
    ]);

    // Go to the protected view in the same page/context, retaining cookies.
    await page.goto(new URL('/private/', baseUrl), {
      waitUntil: 'domcontentloaded',
      timeout: 30_000,
    });

    // Replace with a condition unique to the expected authenticated page.
    await page.locator('[data-testid="private-dashboard"]').wait();
    console.log('Protected view loaded:', page.url());
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(error.message);
  process.exitCode = 1;
});

The Promise.all pairing matters when the submit click causes a full navigation: the navigation wait is registered before the click. Puppeteer documents this pattern to avoid missing a quick navigation. If login happens through an asynchronous request without navigating, do not wait forever for navigation; wait for a site-specific authenticated-state element or a known URL/state change instead. Puppeteer Page API

The sample waits for data-testid="private-dashboard" as a success signal. Choose an element that is only present after authorization. A page load completing is not proof of login: Django may have redirected back to the login page or rendered an access-denied screen.

3. Understand CSRF and session cookies

Django’s CSRF middleware protects unsafe methods such as POST. A normal rendered form commonly includes a hidden CSRF input, while Django may also set a csrftoken cookie. Submitting the rendered form in the same browser context keeps the relevant page state and cookies together. Follow the target application’s CSRF configuration if you automate a custom request; do not disable CSRF protection to make a script pass. Django CSRF documentation

Submitting the rendered form keeps its CSRF state and session cookies in the same browser context.
Submitting the rendered form keeps its CSRF state and session cookies in the same browser context.

Django rotates the CSRF token at login. If your flow fetched a token before logging in and then makes another protected POST, reload the relevant page and obtain a fresh token. Cookie values and token values are credentials: avoid exposing them in traces, screenshots, exception messages, or debug output.

For subsequent navigation, keep using the same Puppeteer page or browser context. A new unrelated context will not automatically have the session established by the login page. Cookie domain, path, secure flag, expiry, and browser context all affect whether a cookie is sent to a URL.

4. Choose the right wait and success condition

Application behavior What to wait for Why
Form submit causes a document navigation waitForNavigation() paired with the click in Promise.all Prevents a race between the click and navigation wait.
Login is submitted asynchronously A visible authenticated element, URL change, or application state No full document navigation may occur.
Protected route redirects after login The final protected-page marker after navigating to the route Verifies authorization rather than merely form submission.
Page loads slowly or assets keep the network busy A stable DOM marker, with an explicit timeout Network-idle conditions can be inappropriate for pages with long-lived requests.

Use the least broad wait that represents the condition you need. A fixed delay can be useful for a known animation or delayed widget, but it is a weak substitute for waiting on a concrete state. Set timeouts so a broken login does not hang a scheduled job indefinitely.

5. Reuse a session only when the workflow needs it

For a single run, retaining the session in the page or context is usually simplest. If you deliberately persist browser state between runs, treat the saved state as a secret and protect it with the same care as the account password. Expiry and invalidation depend on Django’s configured session backend and expiry policy; do not assume a session remains valid for a fixed duration.

Puppeteer’s page-level cookie methods are deprecated in favor of browser or BrowserContext cookie APIs. Check the installed Puppeteer version’s cookie guide before retrieving or setting cookies. Prefer logging in through the application when feasible, since manually injecting a cookie depends on correct domain/path/security attributes and a valid session from that site. Puppeteer Cookies guide

For test isolation, use a fresh browser context per test or account, then close it when finished. Reusing a context can be faster, but it also carries cookies, local storage, and other state between operations. Avoid sharing one logged-in context across unrelated users or tenants.

6. Common errors and fixes

Symptom Likely cause Fix
Login POST returns 403 Missing or stale CSRF token, mismatched cookie, or request not staying same-origin Load the actual form first, submit it in the same context, and reload for a fresh token after login before another protected POST.
Protected URL redirects to login Login failed, cookies were lost, cookie scope does not match, or the session expired Check the final URL and authenticated marker; keep the same context and verify deployment cookie/session settings.
Script times out waiting for navigation The form submits asynchronously or validation prevented navigation Wait for a visible success or error state instead, and inspect the form’s validation messages.
Script proceeds before the new page is ready Wait was started after the click or used an unsuitable condition Start navigation wait and click together; then wait for a protected-page marker.
page.authenticate() has no effect It configures HTTP authentication credentials, not a Django form session Submit the application’s login form. Use page.authenticate() only when the server actually requests HTTP authentication. Puppeteer authenticate API
Cookie API warning or missing method Code uses a deprecated or version-mismatched page cookie API Check the installed Puppeteer version and use its browser or context cookie methods.
Login page fields cannot be found Selectors differ, page has not rendered, or an identity provider owns the form Inspect the page structure, wait for the actual form, and handle any identity-provider redirect explicitly.

7. Performance, reliability, and cost

Browser startup and page rendering are usually the main work in a screenshot or browser-automation run. When processing many URLs on one trusted site, reuse a browser process and create isolated contexts as needed instead of launching a new browser for every URL. Bound concurrency: too many simultaneous pages can exhaust memory, saturate CPU, or trigger the target site’s rate limits.

Reliability comes from explicit state checks and bounded retries. Retry transient navigation failures only when the operation is safe to repeat; do not blindly resubmit a form that may have performed a side effect. Keep login and capture steps separate in logs, but redact credentials and cookies. A failed login should fail closed: do not treat a screenshot of the login page as the protected content.

Cost depends on where the browser runs: local compute, a CI runner, or a hosted browser service have different resource and operational costs. This workflow does not require a special Django setting, and no generic benchmark or per-run cost applies across deployments. For a one-off screenshot of a public page, browser setup may be unnecessary; for an authenticated view, a screenshot API must support the authentication and session behavior the site requires.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its one-call endpoint returns a screenshot or PDF, and the parameter names used by other screenshot APIs also work. See the ScreenshotNeo API documentation for supported options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

These calls capture the specified URL. They do not perform the Django login flow shown above; a login-protected view still requires an authentication approach supported by the target application and service.

  • Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed; response headers identify the page verdict and billing status.
  • An MCP server gives AI agents such as Claude and Cursor the take_screenshot, get_page_info, and capture_pdf tools.
  • 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000. Every feature is on every plan.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

FAQ

Can Puppeteer access a Django view without a username and password?

Only if the workflow already has valid authorized session state or the application provides another supported authentication mechanism. A protected Django view normally checks the authenticated session, so a fresh browser needs to establish that state.

Is page.authenticate() the Django login method?

No. It supplies credentials for HTTP authentication challenges. A typical Django login is an application form submission followed by session-cookie handling.

Why does login work but the next POST fail?

Django rotates the CSRF token on login. Reload the form or page and use the fresh token and cookie for a later unsafe request.

Can I use this against any Django site?

The pattern is general, but selectors and authentication steps are site-specific. MFA, external identity providers, CAPTCHA, custom middleware, cookie policy, and redirects can change the required flow.