madhu P
madhu P

Reputation: 121

Puppeteer - Handle basic authentication popup (that appears after clicking on a link)

I am looking for a solution to handle basic authentication popup (with puppeteer=5.2.1). My scenario is as below.

1. Launch an application 
2. Click on a button on the home page
3. It opens a new window (saml) which ask us to enter email

In new window, 
4. Enter email
5. click on Next button
6. Basic authentication popup appears (=> here I need help to handle the popup)

Here is the code to reach until 3rd step. I am able to get reference to new page (saml window)

var browser = await puppeteer.launch({
      headless: false, args: ["--start-maximized"]
    });
 var page = await this.browser.newPage();
await waitUntilElementIsVisible(authMethodDropDown);
await wait(5000);
await page.select(authMethodDropDown, "myoption");
const samlPage = await clickAndWaitForTarget(page, authSubmitButton);
wait(5000);
await samlPage.waitForSelector(samlSignInUserTextField);

Below are various options I tried to handle basic auth popup in 6th step.

Option-1: using page.authenticate()

await samlPage.authenticate({username: `${myuser}`, password: `${mypass}`});
await samlPage.type(samlSignInUserTextField, `${myuser}`);
await samlPage.click(samlSignInNextButton);

=> It continued to load and next page is not displayed. 

Option-2: using setExtraHttpHeaders

const headers = new Map();
headers.set(
   'Autorization',
   `Basic ${new Buffer(`${myuser}:${mypass}`).toString('base64')}`
 );
headers.set('WWW-Authenticate','Negotiate');
headers.set('WWW-Authenticate','NTLM');
await samlPage.setExtraHTTPHeaders(headers);

=> Still basic authentication popup appears. 

Option-3: Tried to handle auth popup using page.keyboard().

await samlPage.keyboard.type("[email protected]");
await samlPage.keyboard.press("Tab");
await samlPage.keyboard.type("mypassword");
await samlPage.keyboard.press("Enter");

=> But it is not typing anything into the fields. 

Can someone please help me on this?

Upvotes: 2

Views: 7675

Answers (3)

sillynutz
sillynutz

Reputation: 11

await page.authenticate({'username':'YOUR_BASIC_AUTH_USERNAME', 'password': 'YOUR_BASIC_AUTH_PASSWORD'});

credit https://dev.to/sonyarianto/puppeteer-quick-tip-how-to-do-basic-authentication-2pe7

Upvotes: -1

madhu P
madhu P

Reputation: 121

The problem is, I was not able to read the page url when authentication popup appears. As an alternative to read the URL I saw below method using which I read the URL and manipulated it to send basic credentials in the form of https://user:password@domain.

var user=encodeURIComponent(`${process.env.vault_user}`)
var password=encodeURIComponent(`${process.env.password}`)

//click on a button on homepage which opens saml window. get page reference to the saml window.
var samlPage = await pagehelpers.clickAndWaitForTarget(authSubmitButton) 

//on saml window
await pagehelpers.enterText(samlSignInUserTextField, `${process.env.vault_user}`, samlPage)
await pagehelpers.clickElement(samlSignInNextButton, samlPage) // after this step we will get auth popup
const ssoRequestLS = await samlPage.waitForRequest(request => request.url().startsWith('https://sso.test.com/adfs/ls/wia'),        )
       modifiedURL = ssoRequestLS.url().replace(
            "sso.test.com/adfs/ls/wia",
            `${user}:${password}@sso.test.com/adfs/ls/wia`,
        )
await pagehelpers.navigateToURL(modifiedURL, samlPage)

async clickAndWaitForTarget(locator, page = this.page) {
        const pageTarget = page.target();
        await page.click(locator);
        const newTarget = await this.browser.waitForTarget(target => target.opener() === pageTarget);
        const newPage = await newTarget.page();
        await newPage.waitForNavigation({ waitUntil: "domcontentloaded" });
        await newPage.waitForSelector("body");
        return newPage;
}

Upvotes: 0

Dan M
Dan M

Reputation: 1272

We had a very similar issue: needed to exchange SSO username/password for Okta Access Token from federated setup OKTA->ADFS->OKTA. While the full flow is quite elaborated and consist of Okta Application, Node.js server and Node.js client, you can find below the Node.js client that:

  • creates the puppeteer session
  • submits username/password
  • follows all the redirects
  • and handles the Basic HTTP Authentication dialog when it pops-up after the redirect

Please note that it works in both "headless" and "headed" modes.

'use strict'

const puppeteer = require('puppeteer')
const loginUrl = `http://localhost:8998/login`

async function interceptAuthChallenge(client, username, password) {
    // registering interception of the ALL Chrome Requests
    await client.send('Network.setRequestInterception', {
        patterns: [{urlPattern: '*'}],
    })

    let authChallengeSubmitted = {}
    await client.on('Network.requestIntercepted', async e => {
        //  console.log(e.request.url)
        console.log(`EVENT INFO: id=${e.interceptionId}\t type=${e.resourceType}\t isNav=${e.isNavigationRequest}\t isAuthChallenge=${e.authChallenge}`)

        if (e.authChallenge) {
            // There was an authChallenge!
            if (authChallengeSubmitted[e.interceptionId]) {
                // this authChallenge was already tried and failed
                delete authChallengeSubmitted[e.interceptionId]
                // Cancel this authChallenge
                await client.send('Network.continueInterceptedRequest', {
                    'interceptionId': e.interceptionId,
                    'authChallengeResponse': {
                        'response': 'CancelAuth'
                    }
                })
            } else {
                // Memorize that this challenge was tried; otherwise, if it fails, this authChallenge will be received again
                authChallengeSubmitted[e.interceptionId] = true

                console.log('responding to SSO with username/password')
                await client.send('Network.continueInterceptedRequest', {
                    'interceptionId': e.interceptionId,
                    'authChallengeResponse': {
                        'response': 'ProvideCredentials',
                        'username': username,
                        'password': password
                    }
                })
            }
        } else {
            // No authChallenge - continue with the request.
            await client.send('Network.continueInterceptedRequest', {
                interceptionId: e.interceptionId,
            })
        }
        // console.log()
    })
}

async function loginWithCDP(username, password) {
    const browser = await puppeteer.launch({
        args: [
            '--disable-setuid-sandbox',
            '--no-sandbox',
            '--ignore-certificate-errors',
        ],
        ignoreHTTPSErrors: true,
        headless: true
    })
    const context = await browser.createIncognitoBrowserContext()
    const page = await context.newPage()
    await page.setViewport({width: 1200, height: 720})

    // phase 1: we are redirected to SSO routing page, where username is entered
    await page.goto(loginUrl, {waitUntil: 'networkidle0'}) // wait until page load
    await page.type('#id-of-the-username-field', username)

    const client = await page.target().createCDPSession()
    await client.send('Network.enable')
    await interceptAuthChallenge(client, username, password)

    // phase 2: right after the "submit" button is pressed
    // another redirect is being issued from the SSO which results 
    // in the modal dialog with Basic HTTP Authentication
    // this is handled by the *interceptAuthChallenge* function
    await Promise.all([
        page.click('#id-of-the-submit-button'),
        page.waitForNavigation({waitUntil: 'networkidle0'}),
    ])
    
    await page.waitForTimeout(4000)
    await browser.close()
    console.log('Done.')
}


loginWithCDP({YOUR_USERNAME}, {YOUR_PASSWORD})

Upvotes: 2

Related Questions