playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Feature] Directly set Proxy-Authorization in Chrome without configuring any other proxy settings

Open jbaldassari opened this issue 3 years ago • 6 comments

Google Cloud Platform has a feature called Identity Aware Proxy (IAP) that facilitates authentication to back-end cloud services. It's basically a reverse proxy that enforces authentication and applies security policies so the back-end services can just assume that all requests they receive are authorized.

If you need to access an IAP-secured service from inside Google Cloud this authentication happens automatically, but if you need to access an IAP service from outside, it is necessary to supply authentication credentials via headers. This can be done via the Authorization header, but in some cases (if your back-end app also uses Authorization) it is necessary to pass these credentials via the Proxy-Authorization header as a bearer token (not basic auth).

The problem (in Chrome/Chromium anyway) is that Proxy-Authorization may not be set explicitly via extraHTTPHeaders. If you do this and then try to navigate to any page Chrome will give you the following error: net::ERR_INVALID_ARGUMENT. If you configure a proxy this header will be automatically generated, but that won't work in this case because we don't actually want to configure any proxy (there is no applicable proxy address), and we want to use bearer rather than basic auth. I realize that this is a Chrome-specific issue and not directly under PlayWright's control, but I thought I would open an issue anyway to provide a real world example when setting this header is required and to discuss possible solutions or workarounds.

Related issue: https://github.com/microsoft/playwright-python/issues/443

jbaldassari avatar Feb 09 '22 15:02 jbaldassari

Let's collect upvotes.

pavelfeldman avatar Feb 09 '22 19:02 pavelfeldman

The only workaround I was able to find so far is to not test this in Chrome. This is a growing trend among some of our customers - lock everything behind IAP and it's really annoying that a security feature from Chrome's side of things doesn't give us any easy way to override it.

QAWolf-EricE avatar Apr 13 '22 20:04 QAWolf-EricE

Hi, i'm having the same problem with Proxy-Authorization when using Chrome browser. I can't authorize my Google Cloud IAP, because it has invalid parameters as mentioned in the thread.

Can we fix this? Thanks!

duchoparsley avatar Jul 19 '22 10:07 duchoparsley

Keen to have this fixed as well

sly-pims avatar Jan 26 '23 04:01 sly-pims

@pavelfeldman, how man upvotes do we need to get this fix prioritized?

twistedpair avatar Feb 24 '23 14:02 twistedpair

more than one year with this issue, I'm also with the exactly same issue

dante-saggin avatar May 24 '23 10:05 dante-saggin

I wonder if loading a Chrome extension with declarativeNetRequest permission can provide the Proxy-Authorization header or this will also hit the same wall as the other methods?

manigest.json
{
  "manifest_version": 3,
  "name": "Playwright Proxy Authenticator",
  "version": "1.0",
  "declarative_net_request": {
    "rule_resources": [
      {
        "id": "google_iap",
        "enabled": true,
        "path": "google_iap.json"
      }
    ]
  },
  "permissions": [
    "declarativeNetRequest",
    "declarativeNetRequestWithHostAccess",
    "declarativeNetRequestFeedback"
  ]
}
google_iap.json
[
  {
    "id": 1,
    "priority": 1,
    "action": {
      "type": "modifyHeaders",
      "requestHeaders": [
        {
          "header": "Proxy-Authorization",
          "operation": "set",
          "value": "Bearer __OMITTED__ [[Generated by Google IAP API Client]]"
        }
      ]
    },
    "condition": {
      "urlFilter": "*.*",
      "resourceTypes": [
        "main_frame"
      ]
    }
  }
]

mohsen1 avatar Jun 21 '23 15:06 mohsen1

I ended solving this problem by installing a chrome extension as part of the test, using persistent Context. I used ModHeader specifically and was able to integrate with Google's IAP (where I needed to set the the proxy-authentication header, since the underlying authentication header was needed for the api the app was using).

EricEidel avatar Jun 21 '23 15:06 EricEidel

Can confirm that Chrome Extension works.

You can build a very simple extension that sets the Proxy-Authorization header. Note that you need to have a persistent context.

Sharing my code that creates the extension. You can fill in the blanks for how to get the header programmatically

Code:

/**
 * @fileoverview
 * Create the Google IAP Proxy Authorizer browser extension.
 */

import fs from 'node:fs';
import path from 'node:path';

async function create() {
  const root = getRepoRoot();
  const destinationDirectory = '/path/to/dist'; // load this via  --load-extension in launch args
  const header = await getGoogleIAPHeader(); // TODO

  const manifest = {
    manifest_version: 3,
    name: 'Google IAP Proxy Authorizer',
    version: '1.0',
    // for debugging
    // background: {
    //   service_worker: 'background.js',
    // },
    declarative_net_request: {
      rule_resources: [
        {
          id: 'ruleset_1',
          enabled: true,
          path: 'ruleset_1.json',
        },
      ],
    },
    permissions: [
      'declarativeNetRequest',
      'declarativeNetRequestWithHostAccess',
      'declarativeNetRequestFeedback',
      'storage',
      'tabs',
    ],
    host_permissions: ['<all_urls>'],
  };
  const ruleset = [
    {
      id: 1,
      priority: 1,
      action: {
        type: 'modifyHeaders',
        requestHeaders: [
          {
            header: 'Proxy-Authorization',
            operation: 'set',
            value: header,
          },
        ],
      },
      condition: {
        requestMethods: ['get', 'post', 'put', 'delete', 'head', 'patch', 'options', 'connect'],

        resourceTypes: [
          'main_frame',
          'sub_frame',
          'stylesheet',
          'script',
          'image',
          'font',
          'object',
          'xmlhttprequest',
          'ping',
          'csp_report',
          'media',
          'websocket',
          'webtransport',
          'webbundle',
          'other',
        ],
      },
    },
  ];

  fs.mkdirSync(destinationDirectory, { recursive: true });
  fs.writeFileSync(
    path.join(destinationDirectory, 'manifest.json'),
    JSON.stringify(manifest, null, 2),
  );
  fs.writeFileSync(
    path.join(destinationDirectory, 'ruleset_1.json'),
    JSON.stringify(ruleset, null, 2),
  );
}

create();

mohsen1 avatar Jun 23 '23 19:06 mohsen1

I found another work-around for setting Proxy-Authorization. You can use the Playwright Route API. For example, given the Playwright Context, you could do something like this:

await context.route(
  (_url: URL) => true,
  async (route: playwright.Route, request: playwright.Request) =>
    route.fulfill({
      response: await context.request.fetch(request, {
        headers: {'Proxy-Authorization': 'Basic: Zm9vOmJhcgo='},
      }),
    }),
);

It may also work with continue instead of fetch/fulfill, but I haven't tested it yet. That would look like this:

await context.route(
  (_url: URL) => true,
  (route: playwright.Route, request: playwright.Request) =>
    route.continue({
      headers: {'Proxy-Authorization': 'Basic: Zm9vOmJhcgo='},
    }),
);

You could also add a check in there to make sure you're only adding Proxy-Authorization if the request URL is for your IAP-protected app.

jbaldassari avatar Jul 21 '23 21:07 jbaldassari