How to isolate client certificates per browser context in Playwright for parallel execution? (Ubuntu)

1 day ago 1
ARTICLE AD BOX

I’m working on a system that performs login using client certificates and I need to run multiple browsers in parallel, each one using a different certificate.

The requirement is to fully isolate each browser instance so that each one uses a specific .pfx certificate without interference from others.

What I’ve tried so far

Using Chrome flag for auto certificate selection --auto-select-certificate-for-urls={"pattern":"*","filter":{}}

Also tried with a specific pattern for the target site.

However, Playwright/Chromium completely ignores this flag in my setup.


Using Chromium policies

I created the file:

/etc/chromium/policies/managed/policies.json

With:

{ "AutoSelectCertificateForUrls": ["{\"pattern\":\"*\",\"filter\":{}}"] } This is also completely ignored.
Using Playwright clientCertificates

I tried using both .pfx and .pem formats with:

clientCertificates: [ { origin: "https://sso.acesso.gov.br", pfxPath: certInfo.pfxPath, passphrase: certInfo.pass, } ] This also does not work in my case.

Additional context

I suspect the issue might be related to how Brazilian certificates are issued or structured, or possibly something related to OpenSSL / NSS handling on Linux.

My setup

I am running isolated browser contexts like this:

export class BrowserFactory { private contexts: Map<number, BrowserContext> = new Map(); async getOrCreateContext(certInfo: CertInfo): Promise<BrowserContext> { const { certId, profilePath } = certInfo; if (this.contexts.has(certId)) { console.log(`[BrowserFactory] Reusing context for cert ${certId}`); return this.contexts.get(certId)!; } console.log(`[BrowserFactory] Creating new context for cert ${certId}`); const isHeadless = process.env.HEADLESS === "true"; if (!fs.existsSync(profilePath)) { throw new Error(`[BrowserFactory] NSS profile not found: ${profilePath}`); } const certDb = path.join(profilePath, "cert9.db"); const keyDb = path.join(profilePath, "key4.db"); if (!fs.existsSync(certDb) || !fs.existsSync(keyDb)) { throw new Error(`[BrowserFactory] Invalid NSS structure: ${profilePath}`); } this.validateNSS(profilePath); try { const nssdbPath = path.join(profilePath, ".pki", "nssdb"); if (!fs.existsSync(nssdbPath)) { console.log(`[BrowserFactory] Preparing Linux NSS structure at: ${nssdbPath}`); fs.mkdirSync(nssdbPath, { recursive: true }); const filesToCopy = ["cert9.db", "key4.db", "pkcs11.txt"]; for (const file of filesToCopy) { const srcFile = path.join(profilePath, file); const dstFile = path.join(nssdbPath, file); if (fs.existsSync(srcFile) && !fs.existsSync(dstFile)) { fs.copyFileSync(srcFile, dstFile); console.log(`[BrowserFactory] Copied NSS file: ${file}`); } } } const browserEnv = { ...process.env, HOME: profilePath }; console.log( `[BrowserFactory] Forcing Chromium to use HOME=${profilePath}` ); const context = await chromium.launchPersistentContext(profilePath, { headless: isHeadless, env: browserEnv, userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36", viewport: { width: 1366, height: 768 }, locale: "pt-BR", timezoneId: "America/Sao_Paulo", args: [ "--disable-blink-features=AutomationControlled", "--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--disable-software-rasterizer", "--disable-setuid-sandbox", "--disable-infobars", "--window-size=1366,768", "--font-render-hinting=none", '--auto-select-certificate-for-urls={"pattern":"*","filter":{}}' ], }); this.contexts.set(certId, context); return context; } catch (err) { console.error( `[BrowserFactory] Failed to start context for cert ${certId}`, err ); throw err; } } private validateNSS(profilePath: string) { console.log(`[BrowserFactory] Validating NSS at: ${profilePath}`); try { const certList = execSync(`certutil -L -d sql:${profilePath}`).toString(); if (!certList) { throw new Error("No certificates found in NSS"); } console.log(`[BrowserFactory] Certificates:\n${certList}`); const keyList = execSync(`certutil -K -d sql:${profilePath}`).toString(); if (!keyList) { throw new Error("No private keys found in NSS"); } console.log(`[BrowserFactory] Private keys:\n${keyList}`); console.log(`[BrowserFactory] NSS validated`); } catch (err) { console.error(`[BrowserFactory] NSS validation failed`, err); throw err; } } async closeAll() { console.log(`[BrowserFactory] Closing ${this.contexts.size} contexts`); for (const [certId, context] of this.contexts.entries()) { try { await context.close(); } catch (err) { console.error( `[BrowserFactory] Error closing context ${certId}`, err ); } } this.contexts.clear(); } }
Read Entire Article