Skip to content

Commit 45ade6b

Browse files
enricodiasGrahamCampbell
authored andcommitted
[1.16] Add Page::authenticate
Sends credentials with Fetch.continueWithAuth when Fetch.authRequired is received. CancelAuth response is used after a failed attempt and an exception is thrown.
1 parent b444d63 commit 45ade6b

5 files changed

Lines changed: 164 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,21 @@ if ($cookieBar) {
683683
}
684684
```
685685

686+
### HTTP Authentication
687+
688+
You can set credentials for HTTP Basic Authentication using `Page::authenticate` before navigating to a protected page or when using a proxy. If the credentials are invalid, an `AuthenticationFailed` exception is thrown.
689+
690+
```php
691+
use HeadlessChromium\Exception\AuthenticationFailed;
692+
693+
try {
694+
$page->authenticate('username', 'password');
695+
$page->navigate('http://example.com/protected')->waitForNavigation();
696+
} catch (AuthenticationFailed $e) {
697+
// invalid credentials
698+
}
699+
```
700+
686701
### Set user agent
687702

688703
You can set up a user-agent per page:
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Chrome PHP.
5+
*
6+
* (c) Soufiane Ghzal <sghzal@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace HeadlessChromium\Exception;
13+
14+
class AuthenticationFailed extends \Exception
15+
{
16+
}

‎src/Page.php‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use HeadlessChromium\Dom\Node;
2222
use HeadlessChromium\Dom\Selector\CssSelector;
2323
use HeadlessChromium\Dom\Selector\Selector;
24+
use HeadlessChromium\Exception\AuthenticationFailed;
2425
use HeadlessChromium\Exception\CommunicationException;
2526
use HeadlessChromium\Exception\EvaluationFailed;
2627
use HeadlessChromium\Exception\InvalidTimezoneId;
@@ -160,6 +161,64 @@ public function getSession(): Session
160161
return $this->target->getSession();
161162
}
162163

164+
/**
165+
* Sets credentials to be used when the page encounters an HTTP authentication challenge.
166+
*
167+
* @throws AuthenticationFailed
168+
* @throws CommunicationException
169+
*/
170+
public function authenticate(string $username, string $password): void
171+
{
172+
$this->assertNotClosed();
173+
174+
$credentialsAttempted = false;
175+
176+
$this->getSession()->on('method:Fetch.authRequired', function (array $params) use ($username, $password, &$credentialsAttempted) {
177+
if ($credentialsAttempted) {
178+
$this->getSession()->sendMessageSync(
179+
new Message('Fetch.continueWithAuth', [
180+
'requestId' => $params['requestId'],
181+
'authChallengeResponse' => [
182+
'response' => 'CancelAuth',
183+
],
184+
])
185+
);
186+
187+
throw new AuthenticationFailed('Authentication failed: invalid credentials.');
188+
}
189+
190+
$credentialsAttempted = true;
191+
192+
$this->getSession()->sendMessageSync(
193+
new Message('Fetch.continueWithAuth', [
194+
'requestId' => $params['requestId'],
195+
'authChallengeResponse' => [
196+
'response' => 'ProvideCredentials',
197+
'username' => $username,
198+
'password' => $password,
199+
],
200+
])
201+
);
202+
});
203+
204+
$this->getSession()->on('method:Fetch.requestPaused', function (array $params) {
205+
$this->getSession()->sendMessageSync(
206+
new Message('Fetch.continueRequest', [
207+
'requestId' => $params['requestId'],
208+
])
209+
);
210+
});
211+
212+
$this->getSession()->sendMessageSync(
213+
new Message('Fetch.enable', [
214+
'handleAuthRequests' => true,
215+
'patterns' => [
216+
['urlPattern' => '*'],
217+
],
218+
])
219+
);
220+
}
221+
163222
/**
164223
* Sets the HTTP header necessary for basic authentication.
165224
*

‎tests/AuthenticationTest.php‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Chrome PHP.
5+
*
6+
* (c) Soufiane Ghzal <sghzal@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace HeadlessChromium\Test;
13+
14+
use HeadlessChromium\BrowserFactory;
15+
use HeadlessChromium\Exception\AuthenticationFailed;
16+
17+
/**
18+
* @covers \HeadlessChromium\Page::authenticate
19+
*/
20+
class AuthenticationTest extends HttpEnabledTestCase
21+
{
22+
private const AUTH_URL = 'basic-auth.php';
23+
private const VALID_USER = 'testuser';
24+
private const VALID_PASS = 'testpass';
25+
26+
public function testAuthenticateAllowsAccessWithValidCredentials(): void
27+
{
28+
$page = (new BrowserFactory())->createBrowser()->createPage();
29+
30+
$page->authenticate(self::VALID_USER, self::VALID_PASS);
31+
$page->navigate(self::sitePath(self::AUTH_URL))->waitForNavigation();
32+
33+
// let's hit it twice to ensure subsequent requests also work
34+
$page->navigate(self::sitePath(self::AUTH_URL))->waitForNavigation();
35+
36+
self::assertStringContainsString('Authenticated', $page->getHtml());
37+
}
38+
39+
public function testAuthenticateThrowsOnInvalidCredentials(): void
40+
{
41+
$this->expectException(AuthenticationFailed::class);
42+
43+
$page = (new BrowserFactory())->createBrowser()->createPage();
44+
45+
$page->authenticate('wrong', 'credentials');
46+
47+
$page->navigate(self::sitePath(self::AUTH_URL))->waitForNavigation();
48+
}
49+
50+
public function testAuthenticateWithoutCredentialsDeniesAccess(): void
51+
{
52+
$page = (new BrowserFactory())->createBrowser()->createPage();
53+
54+
$page->navigate(self::sitePath(self::AUTH_URL))->waitForNavigation();
55+
56+
self::assertStringNotContainsString('Authenticated', $page->getHtml());
57+
}
58+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
$expectedUser = 'testuser';
4+
$expectedPass = 'testpass';
5+
6+
$user = $_SERVER['PHP_AUTH_USER'] ?? null;
7+
$pass = $_SERVER['PHP_AUTH_PW'] ?? null;
8+
9+
if ($user !== $expectedUser || $pass !== $expectedPass) {
10+
\header('WWW-Authenticate: Basic realm="Test"');
11+
\http_response_code(401);
12+
echo 'Unauthorized';
13+
exit;
14+
}
15+
16+
echo 'Authenticated';

0 commit comments

Comments
 (0)