Skip to content

Commit a61b968

Browse files
authored
Merge pull request google#276 from google/content-security-policy-example
Content security policy example
2 parents c4e3380 + e6f1806 commit a61b968

File tree

5 files changed

+176
-23
lines changed

5 files changed

+176
-23
lines changed

examples/examples.css

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ h2 {
1717
color: #222244;
1818
}
1919

20-
h2 {
21-
font-style: italic;
22-
}
23-
2420
header {
2521
padding: 0.5rem 2rem 0.5rem 2rem;
2622
background: #f0f0f4;
@@ -38,4 +34,4 @@ main {
3834

3935
.hidden {
4036
display: none;
41-
}
37+
}

examples/index.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@
5757
<li><a href="/recaptcha-v3-request-scores.php">Request scores</a></li>
5858
</ul>
5959
</li>
60+
<li><h2>General</h2>
61+
<ul>
62+
<li><a href="/recaptcha-content-security-policy.php">Content Security Policy</a></li>
63+
</ul>
64+
</li>
6065
</ul>
6166
</main>
6267

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
/**
3+
* @copyright Copyright (c) 2015, Google Inc.
4+
* @link https://www.google.com/recaptcha
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
require __DIR__ . '/appengine-https.php';
25+
26+
// Initiate the autoloader. The file should be generated by Composer.
27+
// You will provide your own autoloader or require the files directly if you did
28+
// not install via Composer.
29+
require_once __DIR__ . '/../vendor/autoload.php';
30+
31+
// This example shows the use of a Content Security Policy
32+
// https://developers.google.com/web/fundamentals/security/csp/
33+
34+
// First we generate a pseudorandom nonce for each included or inline script
35+
// Nonce for including the reCAPTCHA library
36+
$recaptchaNonce = base64_encode(openssl_random_pseudo_bytes(16));
37+
// Nonce for our inline code
38+
$inlineNonce = base64_encode(openssl_random_pseudo_bytes(16));
39+
40+
// Note: this is not related to reCAPTCHA, but if you enable a CSP like this
41+
// you either need to include either a nonce or appropriate domain for any
42+
// scripts on the page.
43+
// Nonce for including Google Analytics library.
44+
$gaIncNonce = base64_encode(openssl_random_pseudo_bytes(16));
45+
// Nonce for firing the Google Analytics call
46+
$gaCfgNonce = base64_encode(openssl_random_pseudo_bytes(16));
47+
48+
// Send the CSP header
49+
// Try commenting out the various lines to see what effect it has
50+
51+
// NOTE: Always test your policy Content-Security-Policy-Report-Only first to
52+
// ensure you're not blocking any critical functionality. CSP is an important
53+
// security feature but you can break entire sections of your site if you
54+
// implement it incorrectly.
55+
header(
56+
"Content-Security-Policy: "
57+
."default-src 'none'; " // By default we will deny everything
58+
59+
."script-src "
60+
." 'nonce-".$recaptchaNonce."' " // nonce allowing the reCAPTCHA library to be included
61+
." 'nonce-".$inlineNonce."' " // nonce for inline page code
62+
." 'nonce-".$gaIncNonce."' 'nonce-".$gaCfgNonce."'; " // nonces for other scripts
63+
64+
."img-src https://www.gstatic.com/recaptcha/ https://www.google-analytics.com; " // allow images from these URLS
65+
."frame-src https://www.google.com/; " // allow frames from this URL
66+
67+
."style-src 'self'; " // allow style from our own origin
68+
."connect-src 'self'; " // allow the fetch calls to our own origin
69+
);
70+
71+
// Register API keys at https://www.google.com/recaptcha/admin
72+
$siteKey = '';
73+
$secret = '';
74+
75+
// Copy the config.php.dist file to config.php and update it with your keys to run the examples
76+
if ($siteKey == '' && is_readable(__DIR__ . '/config.php')) {
77+
$config = include __DIR__ . '/config.php';
78+
$siteKey = $config['v3']['site'];
79+
$secret = $config['v3']['secret'];
80+
}
81+
82+
// reCAPTCHA supports 40+ languages listed here: https://developers.google.com/recaptcha/docs/language
83+
$lang = 'en';
84+
85+
?>
86+
<!DOCTYPE html>
87+
<html lang="en">
88+
<meta charset="UTF-8">
89+
<meta name="viewport" content="width=device-width,height=device-height,minimum-scale=1">
90+
<link rel="shortcut icon" href="https://www.gstatic.com/recaptcha/admin/favicon.ico" type="image/x-icon"/>
91+
<link rel="canonical" href="https://recaptcha-demo.appspot.com/recaptcha-content-security-policy.php">
92+
<script type="application/ld+json">{ "@context": "http://schema.org", "@type": "WebSite", "name": "reCAPTCHA demo - Content Security Policy", "url": "https://recaptcha-demo.appspot.com/recaptcha-content-security-policy.php" }</script>
93+
<meta name="description" content="reCAPTCHA demo - Content Security Policy" />
94+
<meta property="og:url" content="https://recaptcha-demo.appspot.com/recaptcha-content-security-policy.php" />
95+
<meta property="og:type" content="website" />
96+
<meta property="og:title" content="reCAPTCHA demo - Content Security Policy" />
97+
<meta property="og:description" content="reCAPTCHA demo - Content Security Policy" />
98+
<link rel="stylesheet" type="text/css" href="/examples.css">
99+
<title>reCAPTCHA demo - Content Security Policy</title>
100+
<header>
101+
<h1>reCAPTCHA demo</h1><h2>Content Security Policy</h2>
102+
<p><a href="/">↤ Home</a></p>
103+
</header>
104+
<main>
105+
<?php
106+
if ($siteKey === '' || $secret === ''):
107+
?>
108+
<h2>Add your keys</h2>
109+
<p>If you do not have keys already then visit <kbd> <a href = "https://www.google.com/recaptcha/admin">https://www.google.com/recaptcha/admin</a></kbd> to generate them. Edit this file and set the respective keys in <kbd>$siteKey</kbd> and <kbd>$secret</kbd>. Reload the page after this.</p>
110+
<?php
111+
else:
112+
?>
113+
<p>This example is sending the <kbd>Content-Security-Policy</kbd> header. Look at the source and inspect the network tab for this request to see what's happening. The reCAPTCHA v3 API is being called here, however you can use the same approach for the v2 API calls as well.</p>
114+
<p><strong>NOTE:</strong>This is a sample implementation, the score returned here is not a reflection on your Google account or type of traffic. In production, refer to the distribution of scores shown in <a href="https://www.google.com/recaptcha/admin" target="_blank">your admin interface</a> and adjust your own threshold accordingly. <strong>Do not raise issues regarding the score you see here.</strong></p>
115+
<ol id="recaptcha-steps">
116+
<li class="step0">reCAPTCHA script loading</li>
117+
<li class="step1 hidden"><kbd>grecaptcha.ready()</kbd> fired, calling <pre>grecaptcha.execute('<?php echo $siteKey; ?>', {action: 'examples/csp'})'</pre></li>
118+
<li class="step2 hidden">Received token from reCAPTCHA service, sending to our backend with:
119+
<pre class="token">fetch('/recaptcha-v3-verify.php?token=abc123</pre></li>
120+
<li class="step3 hidden">Received response from our backend: <pre class="response">{"json": "from-backend"}</pre></li>
121+
</ol>
122+
<p><a href="/recaptcha-content-security-policy.php">⟳ Try again</a></p>
123+
124+
<!-- Add the nonce for our inline script to this tag -->
125+
<script nonce="<?php echo $inlineNonce; ?>">
126+
var onloadCallback = function() {
127+
const steps = document.getElementById('recaptcha-steps');
128+
grecaptcha.ready(function() {
129+
document.querySelector('.step1').classList.remove('hidden');
130+
grecaptcha.execute('<?php echo $siteKey; ?>', {action: 'examples/csp'}).then(function(token) {
131+
document.querySelector('.token').innerHTML = 'fetch(\'/recaptcha-v3-verify.php?action=examples/csp&token=\'' + token;
132+
document.querySelector('.step2').classList.remove('hidden');
133+
134+
fetch('/recaptcha-v3-verify.php?action=examples/csp&token='+token).then(function(response) {
135+
response.json().then(function(data) {
136+
document.querySelector('.response').innerHTML = JSON.stringify(data, null, 2);
137+
document.querySelector('.step3').classList.remove('hidden');
138+
});
139+
});
140+
});
141+
});
142+
};
143+
</script>
144+
<!-- Add the nonce value for the reCAPTCHA library to its script tag -->
145+
<script async defer src="https://www.google.com/recaptcha/api.js?render=<?php echo $siteKey; ?>&onload=onloadCallback" nonce="<?php echo $recaptchaNonce; ?>"></script>
146+
147+
<?php
148+
endif;?>
149+
</main>
150+
151+
<!-- Google Analytics - adding both nonces here for the library and the inline code -->
152+
<script async defer src="https://www.googletagmanager.com/gtag/js?id=UA-123057962-1" nonce="<?php echo $gaIncNonce; ?>"></script>
153+
<script async nonce="<?php echo $gaCfgNonce; ?>">window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-123057962-1');</script>

examples/recaptcha-v3-request-scores.php

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@
4949
<meta charset="UTF-8">
5050
<meta name="viewport" content="width=device-width,height=device-height,minimum-scale=1">
5151
<link rel="shortcut icon" href="https://www.gstatic.com/recaptcha/admin/favicon.ico" type="image/x-icon"/>
52-
<link rel="canonical" href="https://recaptcha-demo.appspot.com/recaptcha-v2-request-scores.php">
53-
<script type="application/ld+json">{ "@context": "http://schema.org", "@type": "WebSite", "name": "reCAPTCHA demo - Request scores", "url": "https://recaptcha-demo.appspot.com/recaptcha-v2-request-scores.php" }</script>
52+
<link rel="canonical" href="https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php">
53+
<script type="application/ld+json">{ "@context": "http://schema.org", "@type": "WebSite", "name": "reCAPTCHA demo - Request scores", "url": "https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php" }</script>
5454
<meta name="description" content="reCAPTCHA demo - Request scores" />
55-
<meta property="og:url" content="https://recaptcha-demo.appspot.com/recaptcha-v2-request-scores.php" />
55+
<meta property="og:url" content="https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php" />
5656
<meta property="og:type" content="website" />
5757
<meta property="og:title" content="reCAPTCHA demo - Request scores" />
5858
<meta property="og:description" content="reCAPTCHA demo - Request scores" />
@@ -82,29 +82,27 @@
8282
<li class="step3 hidden">Received response from our backend: <pre class="response">{"json": "from-backend"}</pre></li>
8383
</ol>
8484
<p><a href="/recaptcha-v3-request-scores.php">⟳ Try again</a></p>
85-
8685
<script src="https://www.google.com/recaptcha/api.js?render=<?php echo $siteKey; ?>"></script>
8786
<script>
88-
const steps = document.getElementById('recaptcha-steps');
89-
grecaptcha.ready(function() {
90-
document.querySelector('.step1').classList.remove('hidden');
91-
grecaptcha.execute('<?php echo $siteKey; ?>', {action: 'examples/v3scores'}).then(function(token) {
92-
document.querySelector('.token').innerHTML = 'fetch(\'/recaptcha-v3-verify.php?token=\'' + token;
93-
document.querySelector('.step2').classList.remove('hidden');
87+
const steps = document.getElementById('recaptcha-steps');
88+
grecaptcha.ready(function() {
89+
document.querySelector('.step1').classList.remove('hidden');
90+
grecaptcha.execute('<?php echo $siteKey; ?>', {action: 'examples/v3scores'}).then(function(token) {
91+
document.querySelector('.token').innerHTML = 'fetch(\'/recaptcha-v3-verify.php?action=examples/v3scores&token=\'' + token;
92+
document.querySelector('.step2').classList.remove('hidden');
9493

95-
fetch('/recaptcha-v3-verify.php?token='+token).then(function(response) {
96-
response.json().then(function(data) {
97-
document.querySelector('.response').innerHTML = JSON.stringify(data, null, 2);
98-
document.querySelector('.step3').classList.remove('hidden');
94+
fetch('/recaptcha-v3-verify.php?action=examples/v3scores&token='+token).then(function(response) {
95+
response.json().then(function(data) {
96+
document.querySelector('.response').innerHTML = JSON.stringify(data, null, 2);
97+
document.querySelector('.step3').classList.remove('hidden');
98+
});
9999
});
100100
});
101101
});
102-
});
103-
</script>
102+
</script>
104103
<?php
105104
endif;?>
106105
</main>
107-
108106
<!-- Google Analytics - just ignore this -->
109107
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-123057962-1"></script>
110108
<script>window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-123057962-1');</script>

examples/recaptcha-v3-verify.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@
4040
}
4141

4242
// Effectively we're providing an API endpoint here that will accept the token, verify it, and return the action / score to the page
43+
// In production, always sanitize and validate the input you retrieve from the request.
4344
$recaptcha = new \ReCaptcha\ReCaptcha($secret);
4445
$resp = $recaptcha->setExpectedHostname($_SERVER['SERVER_NAME'])
45-
->setExpectedAction('examples/v3scores')
46+
->setExpectedAction($_GET['action'])
4647
->setScoreThreshold(0.5)
4748
->verify($_GET['token'], $_SERVER['REMOTE_ADDR']);
4849
header('Content-type:application/json');

0 commit comments

Comments
 (0)