Skip to content

Commit

Permalink
docs(auth): running Lighthouse on authenticated pages (#9628)
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark committed Sep 19, 2019
1 parent 364ca59 commit e7a9b5f
Show file tree
Hide file tree
Showing 11 changed files with 423 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ yarn-error.log
proto/scripts/*_pb2.*
proto/scripts/*_pb.*
proto/scripts/*_processed.json

# require any lock file to be checked in explicitly
yarn.lock
42 changes: 42 additions & 0 deletions docs/authenticated-pages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Running Lighthouse on Authenticated Pages

Default runs of Lighthouse load a page as a "new user", with no previous session or storage data. This means that pages requiring authenticated access do not work without additional setup. You have a few options for running Lighthouse on pages behind a login:

## Option 1: Script the login with Puppeteer

[Puppeteer](https://pptr.dev) is the most flexible approach for running Lighthouse on pages requiring authentication.

See [a working demo at /docs/recipes/auth](./recipes/auth).

## Option 2: Leverage logged-in state with Chrome DevTools

The Audits panel in Chrome DevTools will never clear your cookies, so you can log in to the target site and then run Lighthouse. If `localStorage` or `indexedDB` is important for your authentication purposes, be sure to uncheck `Clear storage`.

## Option 3: Pass custom request headers with Lighthouse CLI

CLI:
```sh
lighthouse http://www.example.com --view --extra-headers="{\"Authorization\":\"...\"}"
```

Node:
```js
const result = await lighthouse('http://www.example.com', {
extraHeaders: {
Authorization: '...',
},
});
```

You could also set the `Cookie` header, but beware: it will [override any other Cookies you expect to be there](https://github.com/GoogleChrome/lighthouse/pull/9170). A workaround is to use Puppeteer's [`page.setCookie`](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagesetcookiecookies).

## Option 4: Open a debug instance of Chrome and manually log in

1. Globally install lighthouse: `npm i -g lighthouse` or `yarn global add lighthouse`. `chrome-debug` is now in your PATH. This binary launches a standalone Chrome instance with an open debugging port.
1. Run chrome-debug. This logs the debugging port of your Chrome instance.
1. Navigate to your site and log in.
1. In a separate terminal, run `lighthouse http://mysite.com --port port-number`, using the port number from chrome-debug.

## Option 5: Reuse a prepared Chrome User Profile

This option is currently under development. Track or join the discussion here: [#8957](https://github.com/GoogleChrome/lighthouse/issues/8957).
18 changes: 18 additions & 0 deletions docs/recipes/auth/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @license Copyright 2019 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
'use strict';

module.exports = {
extends: '../../../.eslintrc.js',
env: {
jest: true,
},
rules: {
'new-cap': 0,
'no-console': 0,
'no-unused-vars': 0,
},
};
111 changes: 111 additions & 0 deletions docs/recipes/auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Running Lighthouse on Authenticated Pages with Puppeteer

If you just want to view the code for using Lighthouse with Puppeteer, see [example-lh-auth.js](./example-lh-auth.js).

## The Example Site

There are two pages on the site:

1. `/` - the homepage
2. `/dashboard`

The homepage shows the login form, but only to users that are not signed in.

The dashboard shows a secret to users that are logged in, but shows an error to users that are not.

The server responds with different HTML for each of these pages and session states.

(Optional) To run the server:
```sh
# be in root lighthouse directory
yarn # install global project deps
cd docs/auth
yarn # install deps related to just this recipe
yarn start # start the server on http://localhost:8000
```

Now that the server is started, let's login with Puppeteer and then run Lighthouse:
```sh
node example-lh-auth.js
```
What does this do? Read on....

## Process

Puppeteer - a browser automation tool - can be used to programatically setup a session.

1. Launch a new browser.
1. Navigate to the login page.
1. Fill and submit the login form.
1. Run Lighthouse using the same browser.

First, launch Chrome:
```js
// This port will be used by Lighthouse later. The specific port is arbitrary.
const PORT = 8041;
const browser = await puppeteer.launch({
args: [`--remote-debugging-port=${PORT}`],
// Optional, if you want to see the tests in action.
headless: false,
slowMo: 50,
});
```

Navigate to the login form:
```js
const page = await browser.newPage();
await page.goto('http://localhost:8000');
```

Given a login form like this:
```html
<form action="/login" method="post">
<label>
Email:
<input type="email" name="email">
</label>
<label>
Password:
<input type="password" name="password">
</label>
<input type="submit">
</form>
```

Direct Puppeteer to fill and submit it:
```js
const emailInput = await page.$('input[type="email"]');
await emailInput.type('admin@example.com');
const passwordInput = await page.$('input[type="password"]');
await passwordInput.type('password');
await Promise.all([
page.$eval('.login-form', form => form.submit()),
page.waitForNavigation(),
]);
```

At this point, the session that Puppeteer is managing is now logged in.

Close the page used to log in:
```js
await page.close();
// The page has been closed, but the browser still has the relevant session.
```

Now run Lighthouse, using the same port as before:
```js
// The local server is running on port 8000.
const url = 'http://localhost:8000/dashboard';
// Direct Lighthouse to use the same port.
const result = await lighthouse(url, { port: PORT });
const lhr = result.lhr;

// Direct Puppeteer to close the browser - we're done with it.
await browser.close();
```

All of the above is done in the example script. To run:
```sh
# make sure server is running (see beginning of recipe) ...
node example-lh-auth.js # login via puppeteer and run lighthouse
```
63 changes: 63 additions & 0 deletions docs/recipes/auth/example-lh-auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* @license Copyright 2019 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
'use strict';

/**
* @fileoverview Example script for running Lighthouse on an authenticated page.
* See docs/recipes/auth/README.md for more.
*/

const puppeteer = require('puppeteer');
const lighthouse = require('lighthouse');

// This port will be used by Lighthouse later. The specific port is arbitrary.
const PORT = 8041;

/**
* @param {import('puppeteer').Browser} browser
*/
async function login(browser) {
const page = await browser.newPage();
await page.goto('http://localhost:8000');
await page.waitForSelector('input[type="email"]', {visible: true});

// Fill in and submit login form.
const emailInput = await page.$('input[type="email"]');
await emailInput.type('admin@example.com');
const passwordInput = await page.$('input[type="password"]');
await passwordInput.type('password');
await Promise.all([
page.$eval('.login-form', form => form.submit()),
page.waitForNavigation(),
]);

await page.close();
}

async function main() {
// Direct Puppeteer to open Chrome with a specific debugging port.
const browser = await puppeteer.launch({
args: [`--remote-debugging-port=${PORT}`],
// Optional, if you want to see the tests in action.
headless: false,
slowMo: 50,
});

// Setup the browser session to be logged into our site.
await login(browser);

// The local server is running on port 8000.
const url = 'http://localhost:8000/dashboard';
// Direct Lighthouse to use the same port.
const result = await lighthouse(url, {port: PORT});
// Direct Puppeteer to close the browser as we're done with it.
await browser.close();

// Output the result.
console.log(JSON.stringify(result.lhr, null, 2));
}

main();
11 changes: 11 additions & 0 deletions docs/recipes/auth/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"scripts": {
"start": "node server/server.js"
},
"dependencies": {
"express": "^4.17.1",
"express-session": "^1.16.2",
"lighthouse": "file:../../..",
"morgan": "^1.9.1"
}
}
21 changes: 21 additions & 0 deletions docs/recipes/auth/server/public/dashboard-unauthenticated.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!--
Copyright 2019 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - Unauthenticated</title>
</head>
<body>
you need to be logged in.
<style>
body {
background-color: #CD5C5C;
}
</style>
</body>
</html>
25 changes: 25 additions & 0 deletions docs/recipes/auth/server/public/dashboard.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!--
Copyright 2019 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard</title>
</head>
<body>
<h1>Dashboard</h1>
<div>
secrets here
</div>
<a href="/logout">logout</a>
<style>
body {
background-color: green;
}
</style>
</body>
</html>
27 changes: 27 additions & 0 deletions docs/recipes/auth/server/public/home-unauthenticated.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!--
Copyright 2019 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home - Login</title>
</head>
<body>
<h1>Plz login</h1>
<form class="login-form" action="/login" method="post">
<label>
Email:
<input type="email" name="email">
</label>
<label>
Password:
<input type="password" name="password">
</label>
<input type="submit">
</form>
</body>
</html>
17 changes: 17 additions & 0 deletions docs/recipes/auth/server/public/home.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!--
Copyright 2019 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home - Logged In</title>
</head>
<body>
<h1>Welcome home!</h1>
<span>You are logged in. Go to <a href="/dashboard">the dashboard</a>.</span>
</body>
</html>

0 comments on commit e7a9b5f

Please sign in to comment.