playwright-client-certificate-login
Version:
A playwright script to login using client certificates
210 lines (188 loc) • 6.32 kB
JavaScript
const { chromium } = require('playwright');
const fs = require('fs');
/**
* Authenticates using a client certificate and provides methods to interact with the authenticated session.
*/
class CertificateAuthSession {
/**
* Creates an instance of CertificateAuthSession.
*
* @param {Object} options - The configuration options.
* @param {string} options.origin - The exact origin that the certificate is valid for.
* @param {string} options.url - The URL to navigate to after authentication.
* @param {string} [options.certPath] - Path to the certificate file in PEM format.
* @param {string} [options.keyPath] - Path to the private key file in PEM format.
* @param {string} [options.pfxPath] - Path to the PFX or PKCS12 encoded private key and certificate chain.
* @param {string} [options.passphrase] - Passphrase for the private key (PEM or PFX).
* @param {Buffer} [options.pfxBuffer] - Direct value of the PFX or PKCS12 encoded private key and certificate chain.
* @param {Buffer} [options.certBuffer] - Direct value of the certificate in PEM format.
* @param {Buffer} [options.keyBuffer] - Direct value of the private key in PEM format.
* @param {number} [options.timeout=30000] - Timeout in milliseconds for page load.
*/
constructor(options) {
// Destructure options
const {
origin,
url,
certPath,
keyPath,
pfxPath,
passphrase,
pfxBuffer,
certBuffer,
keyBuffer,
timeout = 30000
} = options;
if (!origin || !url) {
throw new Error('Both "origin" and "url" must be provided in the options.');
}
this.origin = origin;
this.url = url;
this.certPath = certPath;
this.keyPath = keyPath;
this.pfxPath = pfxPath;
this.passphrase = passphrase;
this.pfxBuffer = pfxBuffer;
this.certBuffer = certBuffer;
this.keyBuffer = keyBuffer;
this.timeout = timeout;
// Initialize properties
this.browser = null;
this.context = null;
this.page = null;
this.cookies = [];
this.headers = {};
}
/**
* Authenticates using the provided client certificate.
*
* @returns {Promise<void>}
*/
async authenticate() {
// Prepare client certificate options for browser context
const clientCertificate = {
origin: this.origin,
passphrase: this.passphrase
};
// Add certificate details based on what is provided
if (this.pfxPath || this.pfxBuffer) {
if (this.pfxPath) {
clientCertificate.pfx = fs.readFileSync(this.pfxPath);
} else {
clientCertificate.pfx = this.pfxBuffer;
}
} else if ((this.certPath && this.keyPath) || (this.certBuffer && this.keyBuffer)) {
if (this.certPath && this.keyPath) {
clientCertificate.cert = fs.readFileSync(this.certPath, 'utf8');
clientCertificate.key = fs.readFileSync(this.keyPath, 'utf8');
} else {
clientCertificate.cert = this.certBuffer;
clientCertificate.key = this.keyBuffer;
}
} else {
throw new Error('You must provide either pfxPath/pfxBuffer or certPath & keyPath/certBuffer & keyBuffer.');
}
const contextOptions = {
ignoreHTTPSErrors: true,
clientCertificates: [clientCertificate]
};
// Launch browser
this.browser = await chromium.launch({
headless: true,
ignoreHTTPSErrors: true
});
// Create a new context with the client certificate
this.context = await this.browser.newContext(contextOptions);
this.page = await this.context.newPage();
try {
// Navigate to the URL
await this.page.goto(this.url);
await this.page.waitForLoadState('networkidle', { timeout: this.timeout });
// Get all cookies after authentication
this.cookies = await this.context.cookies();
// Filter and prepare headers for the API call
const filteredCookies = this.cookies.filter(cookie =>
(cookie.name === 'X-CSRF-RME-Angular') ||
(cookie.name === 'IDP_SESSION_MARKER_accounts') ||
((cookie.name === '__VCAP_ID__' || cookie.name === 'JSESSIONID') && cookie.domain === 'roadmaps.sap.com')
);
// Simplified cookie filtering - remove SAP-specific filters
const headerCookiesString = filteredCookies.map(cookie =>
`${cookie.name}=${cookie.value}`
).join('; ');
// Generic headers
this.headers = {
'cookie': headerCookiesString,
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-language": "de-DE,de;q=0.9",
"cache-control": "max-age=0",
"priority": "u=0, i",
"sec-ch-ua": "\"Chromium\";v=\"129\", \"Not=A?Brand\";v=\"8\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "same-site",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
"Referer": this.url,
"Referrer-Policy": "origin"
};
} catch (error) {
console.error('An error occurred during authentication:', error);
await this.browser.close();
throw error;
}
}
/**
* Returns the cookies obtained after authentication.
*
* @returns {Array<Object>} - The cookies.
*/
getCookies() {
return this.cookies;
}
/**
* Returns the headers prepared for API calls.
*
* @returns {Object} - The headers.
*/
getHeaders() {
return this.headers;
}
/**
* Returns the Playwright browser instance.
*
* @returns {Browser} - The Playwright browser instance.
*/
getBrowser() {
return this.browser;
}
/**
* Returns the Playwright browser context.
*
* @returns {BrowserContext} - The Playwright browser context.
*/
getContext() {
return this.context;
}
/**
* Returns the Playwright page instance.
*
* @returns {Page} - The Playwright page instance.
*/
getPage() {
return this.page;
}
/**
* Closes the browser instance.
*
* @returns {Promise<void>}
*/
async close() {
if (this.browser) {
await this.browser.close();
}
}
}
module.exports = { CertificateAuthSession };