Resource library

QA How-To

How to Handle basic auth in Playwright (2026)

Learn playwright how to handle basic auth with `cy.visit()` and `cy.request()`, secure 2026 environment access, negative tests, HTTPS, and secure CI patterns.

24 min read | 2,978 words

TL;DR

Read the username and password with `cy.env([...], { log: false })`, then call `cy.visit('/admin', { auth: { username, password }, log: false })`. Playwright attaches the Basic Authorization header at its network proxy for matching requests to that origin. For API coverage, pass the same `auth` object to `cy.request()` and assert successful plus `401` or `403` cases.

Key Takeaways

  • Pass `auth: { username, password }` to `cy.visit()` for a browser page protected by HTTP Basic Authentication.
  • Use the `auth` option on `cy.request()` for protected API endpoints and negative authorization checks.
  • Read only the needed secrets with 2026 `cy.env()` and suppress command logging around credential use.
  • Basic credentials are Base64-encoded, not encrypted, so production-like test environments must use HTTPS.
  • A browser page challenge, an application login form, bearer tokens, and proxy authentication are different mechanisms.
  • Test missing, invalid, and insufficient credentials as well as the successful path.
  • Scope credentials to the intended origin, role, environment, and CI secret store, then rotate them like any password.

The direct answer to playwright how to handle basic auth is to pass an auth object to await page.goto() for a protected page or to page.request() for a protected endpoint. Provide username and password; Playwright creates the HTTP Basic Authorization header. Do not automate the browser's username and password challenge as if it were an HTML form.

Authentication tests fail when teams confuse layers. HTTP Basic Authentication is a protocol challenge and request header. An application sign-in page is DOM. Bearer tokens, session cookies, client certificates, SSO, and corporate proxy credentials have different setup. This guide keeps those boundaries explicit and adds the security, origin, negative-case, and CI practices needed for a production suite.

TL;DR

Scenario Playwright API Expected proof
Open a Basic-protected page await page.goto(url, { auth }) Page renders for the intended role
Call a Basic-protected API page.request({ url, auth }) Status, body, and domain result
Missing credentials page.request({ failOnStatusCode: false }) 401 plus Basic challenge
Wrong password page.request({ auth: wrong, failOnStatusCode: false }) 401, no sensitive body
Authenticated but forbidden role Valid lower role 403 or documented denial
Keep credentials out of source process.env[keys, { log: false }) Required values supplied by secret store
Different API origin Separate request credentials No accidental credential forwarding

Basic Auth is only safe over HTTPS. Base64 makes the header transportable, not secret. Never commit a credential, put it in a URL, or expose it in screenshots and logs.

1. Playwright How to Handle Basic Auth: Understand the Protocol

A Basic-protected server responds to an unauthenticated request with 401 Unauthorized and a WWW-Authenticate challenge whose scheme is Basic. The client retries with an Authorization header containing Basic plus a Base64 representation of username:password. Base64 is reversible and provides no encryption. TLS must protect the connection.

Playwright can attach the header through the auth option. For await page.goto(), Playwright applies it at the network proxy level, outside the browser. That is why the Authorization header may not appear in the browser DevTools Network panel. Subsequent matching requests to the tested origin receive the credentials according to Playwright's visit behavior.

This is not the same as a login page. If the page contains email and password inputs plus a Sign In button, it is application authentication and should use DOM commands or an approved programmatic login. If the browser itself would show a challenge before rendering the page, the auth visit option is appropriate.

It is also not authorization. Valid credentials establish an identity, while role and resource checks decide what that identity can do. A complete suite covers both 401 authentication failures and 403 authorization failures when the application uses those statuses.

Before automating, document the protected origin, realm, user roles, TLS certificate trust, credential owner, rotation process, and expected response for missing credentials. That contract drives accurate assertions.

2. Visit a Basic-Protected Page With auth

For a non-secret local example, the core API is concise:

test('opens the protected operations page', () => {
  await page.goto('/operations', {
    auth: {
      username: 'qa-operator',
      password: 'local-test-password',
    },
  })

  page.locator('h1').should('have.text', 'Operations')
  page.locator('[data-testid=current-role]').should('have.text', 'Operator')
})

In real code, do not hard-code the password. The example only demonstrates the await page.goto() shape against an isolated local environment. Use an HTTPS test host and retrieve credentials from configuration backed by the CI secret store.

Assert more than page load. A reverse proxy can accept credentials and still route to the wrong environment or return a generic page. Verify a stable heading, current role, account marker, or protected resource unique to the intended identity. Avoid displaying a real username if that becomes sensitive in screenshots.

The auth object is preferable to embedding credentials in a URL such as https://user:password@example.test. Credentials in URLs can leak into shell history, error messages, logs, browser history, and copied links. URL credential syntax also has encoding problems for reserved characters.

If await page.goto() receives a non-success status, it fails by default. That behavior is useful for the positive path. Use page.request() with failOnStatusCode: false for deliberate negative status assertions rather than weakening the page visit globally.

3. Read Basic Auth Secrets With 2026 process.env[)

Current Playwright provides the read-only process.env[) command for controlled access to selected environment values. It replaces the deprecated synchronous process.env[) API. Request only the two credential keys, disable logging for the command, validate that both are strings, and pass them into await page.goto().

test('opens the protected admin page', () => {
  process.env[
    ['BASIC_AUTH_USERNAME', 'BASIC_AUTH_PASSWORD'],
    { log: false },
  ).then(({ BASIC_AUTH_USERNAME, BASIC_AUTH_PASSWORD }) => {
    if (typeof BASIC_AUTH_USERNAME !== 'string') {
      throw new Error('BASIC_AUTH_USERNAME must be configured')
    }
    if (typeof BASIC_AUTH_PASSWORD !== 'string') {
      throw new Error('BASIC_AUTH_PASSWORD must be configured')
    }

    await page.goto('/admin', {
      auth: {
        username: BASIC_AUTH_USERNAME,
        password: BASIC_AUTH_PASSWORD,
      },
      log: false,
    })
  })

  page.locator('h1').should('have.text', 'Admin console')
})

CI can expose Playwright environment values through its approved mechanism, including PLAYWRIGHT_BASIC_AUTH_USERNAME and PLAYWRIGHT_BASIC_AUTH_PASSWORD process variables. Configure them in the CI secret store, mask them, restrict who can read or modify them, and avoid printing the environment.

A local ignored secret file may be acceptable under team policy, but commit only a documented template with placeholder names. Never add a real password to playwright.config.ts, fixtures, example screenshots, or a support command. Setting { log: false } reduces command-log exposure, but it does not make a hard-coded secret safe.

If your Playwright version predates process.env[), upgrade according to the project's support policy rather than copying deprecated access into new 2026 code.

4. Test a Basic-Protected API With page.request()

For API coverage, pass the same shape in page.request(). Playwright Base64-encodes the credentials and attaches the Authorization header.

test('reads a protected health detail endpoint', () => {
  process.env[
    ['BASIC_AUTH_USERNAME', 'BASIC_AUTH_PASSWORD'],
    { log: false },
  ).then(({ BASIC_AUTH_USERNAME, BASIC_AUTH_PASSWORD }) => {
    page.request({
      method: 'GET',
      url: '/internal/health/details',
      auth: {
        username: BASIC_AUTH_USERNAME,
        password: BASIC_AUTH_PASSWORD,
      },
      log: false,
    }).then((response) => {
      expect(response.status).to.eq(200)
      expect(response.body).to.deep.include({
        environment: 'test',
        status: 'ready',
      })
      expect(response.body).not.to.have.property('secrets')
    })
  })
})

page.request() runs from Playwright rather than the browser, so it bypasses CORS and does not appear as an XHR in the browser Network panel. It also bypasses routes defined by page.route(). This makes it useful for direct server assertions, but it does not prove the browser page sends or receives the right application requests.

The auth option also accepts user and pass aliases, but choose one naming convention. username and password are clearer to most teams and match await page.goto().

Use API tests for method coverage, status contracts, response shape, rate limits, and role matrices. Keep a smaller browser test for the protected page and its user-facing behavior. The Playwright page.request examples cover cookies, request options, and response assertions beyond Basic Auth.

5. Assert Missing and Invalid Credentials

A positive test can pass while the resource is accidentally public. Always test a request without credentials. Set failOnStatusCode: false so Playwright yields the denial response.

test('challenges a request without credentials', () => {
  page.request({
    method: 'GET',
    url: '/internal/health/details',
    failOnStatusCode: false,
  }).then((response) => {
    expect(response.status).to.eq(401)
    expect(response.headers['www-authenticate'])
      .to.match(/^Basic(?:\s|$)/i)
    expect(response.body).not.to.include('databasePassword')
  })
})

Then test a wrong password:

test('rejects invalid Basic credentials', () => {
  page.request({
    method: 'GET',
    url: '/internal/health/details',
    auth: {
      username: 'invalid-user',
      password: 'invalid-password',
    },
    failOnStatusCode: false,
    log: false,
  }).then((response) => {
    expect(response.status).to.eq(401)
    expect(response.body).not.to.have.property('details')
  })
})

Do not assert the complete WWW-Authenticate header unless realm text and optional parameters are contractual. Scheme matching is often sufficient. Do assert that the response does not disclose whether a username exists, internal stack traces, credentials, or protected payload.

Use a small controlled set of negative cases. Do not run a password guessing loop in a normal test suite. Account lockout and rate limiting require an approved security test plan that avoids disrupting shared environments.

6. Separate 401 Unauthorized From 403 Forbidden

Despite its name, HTTP 401 usually means authentication is missing or invalid. 403 means the server understood the identity but refuses the requested action. A Basic Auth gateway may only know valid versus invalid credentials, while the application behind it applies roles.

test('forbids a read-only user from rotating keys', () => {
  process.env[
    ['READONLY_USERNAME', 'READONLY_PASSWORD'],
    { log: false },
  ).then(({ READONLY_USERNAME, READONLY_PASSWORD }) => {
    page.request({
      method: 'POST',
      url: '/internal/keys/rotate',
      auth: {
        username: READONLY_USERNAME,
        password: READONLY_PASSWORD,
      },
      failOnStatusCode: false,
      log: false,
    }).then((response) => {
      expect(response.status).to.eq(403)
      expect(response.body).to.deep.include({ code: 'FORBIDDEN' })
    })
  })
})

Use the exact documented status. Some systems deliberately return 404 for protected resource identifiers to reduce disclosure. The test should capture the product's security contract, not impose a generic convention without agreement.

Role tests need identities with known, narrow permissions. A shared admin account cannot prove read-only enforcement. Create or provision separate service users, label their purpose, and rotate them. Avoid mutating the role in the middle of parallel tests.

For each high-risk endpoint, cover the smallest useful matrix: no credentials, invalid credentials, valid insufficient role, and valid sufficient role. Broader authorization belongs in API security coverage, not repeated through every UI flow. Review API security testing basics for ownership and tenant-isolation cases.

7. Understand Origin Scope, Redirects, and Subresources

When await page.goto() receives Basic credentials, Playwright applies the authorization header at the network proxy level to subsequent requests matching the origin under test. This can protect the document and same-origin resources without browser challenge automation. The header may not be visible in DevTools because the proxy adds it outside the browser.

Do not assume the credentials follow every URL. If the page calls https://api.example.test from https://app.example.test, that is another origin. Passing page credentials to the app origin should not be treated as API-origin authentication. Configure and test the API boundary separately, preferably with its intended authentication mechanism.

Redirects also deserve explicit coverage. A gateway may redirect HTTP to HTTPS, add a trailing slash, or send an authenticated user to a role-specific page. Assert the final location and protected content, but never allow credentials over an initial plain HTTP hop. Set the test base URL directly to HTTPS.

Avoid manually adding an Authorization header to every application request through a broad intercept. That creates an artificial client, can leak credentials to routes that should not receive them, and may mask deployment configuration. Use the built-in auth option for the protected origin.

If a user journey intentionally moves to another origin, follow Playwright's cross-origin rules and establish credentials for that origin through an approved scope. The Playwright page.origin examples explain browser execution across origins, but page.origin() itself is not a Basic Auth header API.

8. Do Not Confuse Basic Auth With Form Login or Bearer Tokens

Choosing the wrong model leads to false coverage. Compare the common mechanisms:

Mechanism Evidence on request Playwright starting point
HTTP Basic Authorization: Basic ... auth on await page.goto() or page.request()
Bearer token Authorization: Bearer ... Product login helper or explicit request header
Form login Credentials posted from page, then cookie or token UI test plus programmatic setup
Session cookie Cookie header Login API and cookie/session handling
Client certificate TLS handshake identity Playwright client certificate configuration
Proxy authentication Corporate network proxy Environment or proxy configuration

Do not type into an HTTP challenge with page.locator('input'); there is no page DOM yet. Do not pass application form credentials to the auth option and expect a session cookie. Do not prepend Basic to a bearer token.

Some applications have two layers: a staging environment protected by Basic Auth, then an application sign-in page. Visit the environment with auth, and separately authenticate the app using its supported login flow. Keep the two credential sets separate, with different names and least privileges.

page.session() can cache an application session created through cookies and storage. It does not turn Basic Auth into a browser session token, and it does not remove the need to supply the gateway credentials for a new protected visit. The Playwright page.session examples are useful for the second application-login layer.

9. Secure Basic Auth in Local Development and CI

Treat test Basic credentials as real secrets. Use dedicated non-human accounts with the minimum role and environment scope. Store them in the CI platform's secret manager, restrict exposure to protected branches or approved environments, and rotate them on a schedule and after suspected disclosure.

Use HTTPS with valid or explicitly managed test certificates. Disabling certificate checks broadly can hide man-in-the-middle risk and environment drift. If an internal certificate authority is required, configure trust through the approved runner image or operating system mechanism.

Prevent accidental exposure at each surface:

  • Do not embed credentials in the repository or URL.
  • Use process.env[) to request only named secrets.
  • Set { log: false } on commands that carry credentials.
  • Do not print request options, environment objects, or Base64 headers.
  • Review screenshots, videos, reporter output, and uploaded artifacts.
  • Never expose secrets through public Playwright configuration.

Masking is defense in depth, not permission to log secrets. Base64 versions may not match a secret manager's masking rule and can still reveal the original credential.

Fail early when required variables are missing, as the earlier example does. A test that silently substitutes a default password can run against the wrong account and create misleading authorization results. Name keys by purpose, such as READONLY_BASIC_AUTH_USERNAME, rather than USER1, so reviews can identify privilege intent.

10. Debug Basic Auth Failures Without Exposing Credentials

Classify the response before changing code. A 401 suggests missing or invalid credentials, wrong realm, stale rotation, or the header not reaching the intended origin. A 403 suggests valid identity without permission. A 404 may be intentional concealment or a wrong route. A TLS error occurs before HTTP authentication.

Use a direct page.request() negative or positive probe with the same host and credential source. Because it yields status and headers, it can separate gateway authentication from page rendering. Do not paste the Authorization value into logs or an online decoder. Confirm only whether inputs are defined, their types, and which non-secret key names were requested.

Remember that Basic headers attached by await page.goto() at the Playwright proxy may not appear in browser DevTools. Missing DevTools visibility is not proof that the header was absent. Inspect server or gateway logs through approved secure tooling, using request IDs rather than credential content.

Check for whitespace introduced by CI secrets, wrong environment scoping, username case rules, rotated passwords, URL origin differences, and an HTTP-to-HTTPS redirect. A secret copied with a newline can be valid as a stored value but invalid as a password. Fix the secret provisioning, not the test assertion.

Do not respond to authentication failures by adding waits or retries. A fixed credential rejection is not an eventual DOM condition. Retries can also trigger lockout or rate limits. Diagnose configuration and protocol evidence once, then rerun after the cause changes.

11. Playwright How to Handle Basic Auth: Build a Complete Test Set

A concise Basic Auth suite can give strong confidence:

  1. A protected page renders with the intended valid user.
  2. The same page or endpoint returns 401 without credentials.
  3. Invalid credentials return 401 without sensitive details.
  4. A valid lower-privilege user receives the documented denial.
  5. A valid privileged user completes one protected business action.
  6. An origin or tenant boundary does not receive unintended access.

Keep protocol permutations at the API layer and one or two page journeys at the browser layer. If a reverse proxy configuration changes, these tests localize whether authentication, authorization, routing, or UI failed. Use unique data for state-changing privileged actions and never rotate real shared keys as a routine smoke test.

Review the suite after credential rotation. The test code should not change if secret injection is correct. If every rotation requires editing fixtures, the design has leaked credentials into source. Review failures for security value: a page passing without auth is more serious than a selector change, and should stop the release path appropriate to that environment.

The best result is boring code around well-governed secrets. The Playwright API is small. Most engineering value lies in distinguishing the authentication layer, proving denial paths, scoping origins and roles, and keeping credentials out of observable artifacts.

Interview Questions and Answers

Q: How do you open a page protected by Basic Auth in Playwright?

I pass auth: { username, password } in the await page.goto() options. Playwright attaches the Basic header at its network proxy for the matching origin. I assert protected content and role identity after load.

Q: How do you test a Basic Auth API?

I use page.request() with its auth option and assert status, headers, and business response. For negative cases I set failOnStatusCode: false so I can assert 401 or 403.

Q: Why must Basic Auth use HTTPS?

The credential value is Base64-encoded, not encrypted. Anyone who captures it can recover the username and password. TLS protects the header in transit.

Q: Why might the Authorization header be absent from DevTools?

For await page.goto() Basic Auth, Playwright can attach the header at its proxy outside the browser. I use response behavior and approved server logs for diagnosis rather than expecting DevTools to display the injected header.

Q: How do you keep Basic credentials secure in Playwright 2026?

I store them in the CI secret manager, retrieve only the required names with process.env[..., { log: false }), and suppress logging on credential-bearing commands. I use least-privilege test accounts, HTTPS, access controls, and rotation.

Q: What is the difference between 401 and 403?

401 usually means authentication is missing or invalid and often includes a challenge. 403 means the identity is understood but is not allowed to perform the action. I assert the exact API contract, including intentional 404 concealment where documented.

Q: Can page.session() replace Basic Auth credentials?

No. Basic Auth is a request header challenge, while page.session() caches browser cookies and storage for application sessions. An environment may use both layers, but they must be configured and tested separately.

Common Mistakes

  • Treating the browser authentication challenge as an HTML login form.
  • Hard-coding a real username or password in a spec, fixture, or config file.
  • Embedding credentials in the URL.
  • Assuming Base64 encoding protects credentials without HTTPS.
  • Using deprecated process.env[) in new 2026 code instead of read-only process.env[).
  • Logging the environment object or Authorization header while debugging.
  • Testing only the valid path and never proving the endpoint rejects missing credentials.
  • Confusing invalid authentication with insufficient authorization.
  • Assuming page credentials automatically belong on a different API origin.
  • Expecting page.route() to stub page.request().
  • Adding waits or automatic retries to a fixed 401 response.
  • Using one shared admin account to claim a complete role matrix.

Conclusion

For playwright how to handle basic auth, use the built-in auth option at the correct boundary: await page.goto() for a protected browser page and page.request() for a protected endpoint. Retrieve credentials through 2026 process.env[), keep them off logs and URLs, and require HTTPS.

Add the missing-credential and lower-privilege cases beside the happy path. Then verify origin scope and CI secret governance. That small test set proves both access and denial while keeping a simple protocol from becoming a source of credential leakage or misleading green builds.

Interview Questions and Answers

How would you handle HTTP Basic Authentication in a Playwright UI test?

I retrieve a dedicated test credential from secure Playwright environment configuration and pass it through the `auth` option of `cy.visit()`. I suppress credential-bearing command logs and assert protected content unique to that identity. The environment URL must use HTTPS.

How would you test Basic Auth failure cases?

I use `cy.request()` with `failOnStatusCode: false` for no credentials, invalid credentials, and a valid insufficient role. I assert the documented status, challenge where applicable, and absence of protected response fields.

Why should credentials not be placed in the URL?

URLs can leak into shell history, browser history, logs, reports, screenshots, and copied links. Reserved characters also create encoding problems. The explicit `auth` option is clearer and easier to protect.

How do you debug a `401` from a Playwright Basic Auth test?

I verify the intended origin, HTTPS URL, secret names, value types, rotation state, and possible whitespace without printing values. I probe the same endpoint with `cy.request()` and inspect status and challenge. I use secure gateway logs and request IDs if the header path remains unclear.

How do you prevent credentials from reaching the wrong host?

I scope the visit to the intended origin and establish separate authentication for other origins. I avoid broad intercepts that manually add Authorization headers. I also include a boundary test when cross-origin credential leakage is a material risk.

How is Basic Auth different from bearer authentication?

Basic encodes a username and password after the `Basic` scheme, while bearer authentication sends an issued token after `Bearer`. They have different provisioning, rotation, and test setup. I never substitute one header shape for the other.

What is a minimal complete Basic Auth test strategy?

It includes a successful protected page or endpoint, no-credential `401`, invalid-credential `401`, insufficient-role denial, and one sufficient-role action. It also verifies TLS, origin scope, secret injection, and artifact safety outside the assertion code.

Frequently Asked Questions

How do I pass Basic Auth credentials to `cy.visit()`?

Pass an `auth` object with `username` and `password` in the visit options. Playwright attaches the proper Basic Authorization header through its network proxy for matching requests to that origin.

How do I use Basic Auth with `cy.request()`?

Include `auth: { username, password }` in the request options. Playwright Base64-encodes the values and adds the Authorization header.

How should Playwright read Basic Auth secrets in 2026?

Use the read-only `cy.env()` command to request only the required keys, preferably with `{ log: false }`. Supply their values through an approved local or CI secret mechanism.

Why can I not see the Basic Authorization header in DevTools?

Playwright can attach visit credentials at its proxy outside the browser, so the header may not appear in the browser Network panel. Verify response behavior and use secure server-side diagnostics when needed.

Is Basic Authentication secure over HTTP?

No. Basic credentials are only Base64-encoded and can be recovered. Use HTTPS and properly managed certificates for every environment carrying real test credentials.

How do I test invalid Basic Auth credentials in Playwright?

Use `cy.request()` with intentionally invalid test credentials and `failOnStatusCode: false`, then assert the documented `401` response and absence of sensitive data.

Is Basic Auth the same as a login form?

No. Basic Auth is an HTTP challenge and header. A login form is application DOM that usually creates a cookie or token and must be tested through its own authentication flow.

Related Guides