How to validate Google reCAPTCHA v3 on server side?

135,045

Solution 1

this is solution

index.html

<html>
  <head>
    <title>Google recapcha demo - Codeforgeek</title>
    <script src='https://www.google.com/recaptcha/api.js'></script>
  </head>
  <body>
    <h1>Google reCAPTHA Demo</h1>
    <form id="comment_form" action="form.php" method="post">
      <input type="email" placeholder="Type your email" size="40"><br><br>
      <textarea name="comment" rows="8" cols="39"></textarea><br><br>
      <input type="submit" name="submit" value="Post comment"><br><br>
      <div class="g-recaptcha" data-sitekey="=== Your site key ==="></div>
    </form>
  </body>
</html>

verify.php

<?php
    $email; $comment; $captcha;

    if(isset($_POST['email']))
        $email=$_POST['email'];
    if(isset($_POST['comment']))
        $comment=$_POST['comment'];
    if(isset($_POST['g-recaptcha-response']))
        $captcha=$_POST['g-recaptcha-response'];

    if(!$captcha){
        echo '<h2>Please check the the captcha form.</h2>';
        exit;
    }

    $response = json_decode(file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=YOUR SECRET KEY&response=".$captcha."&remoteip=".$_SERVER['REMOTE_ADDR']), true);
    if($response['success'] == false)
    {
        echo '<h2>You are spammer ! Get the @$%K out</h2>';
    }
    else
    {
        echo '<h2>Thanks for posting comment.</h2>';
    }
?>

http://codeforgeek.com/2014/12/google-recaptcha-tutorial/

Solution 2

Private key safety

While the answers here are definately working, they are using a GET request, which exposes your private key (even though https is used). On Google Developers the specified method is POST.

For a little bit more detail: https://stackoverflow.com/a/323286/1680919

Verification via POST

function isValid() 
{
    try {

        $url = 'https://www.google.com/recaptcha/api/siteverify';
        $data = ['secret'   => '[YOUR SECRET KEY]',
                 'response' => $_POST['g-recaptcha-response'],
                 'remoteip' => $_SERVER['REMOTE_ADDR']];
                 
        $options = [
            'http' => [
                'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
                'method'  => 'POST',
                'content' => http_build_query($data) 
            ]
        ];
    
        $context  = stream_context_create($options);
        $result = file_get_contents($url, false, $context);
        return json_decode($result)->success;
    }
    catch (Exception $e) {
        return null;
    }
}

Array Syntax: I use the "new" array syntax ( [ and ] instead of array(..) ). If your php version does not support this yet, you will have to edit those 3 array definitions accordingly (see comment).

Return Values: This function returns true if the user is valid, false if not, and null if an error occured. You can use it for example simply by writing if (isValid()) { ... }

Solution 3

I'm not a fan of any of these solutions. I use this instead:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
    'secret' => $privatekey,
    'response' => $_POST['g-recaptcha-response'],
    'remoteip' => $_SERVER['REMOTE_ADDR']
]);

$resp = json_decode(curl_exec($ch));
curl_close($ch);

if ($resp->success) {
    // Success
} else {
    // failure
}

I'd argue that this is superior because you ensure it is being POSTed to the server and it's not making an awkward 'file_get_contents' call. This is compatible with recaptcha 2.0 described here: https://developers.google.com/recaptcha/docs/verify

I find this cleaner. I see most solutions are file_get_contents, when I feel curl would suffice.

Solution 4

Easy and best solution is the following.
index.html

<form action="submit.php" method="POST">
   <input type="text" name="name" value="" />
   <input type="text" name="email" value="" />
   <textarea type="text" name="message"></textarea>
   <div class="g-recaptcha" data-sitekey="Insert Your Site Key"></div>
   <input type="submit" name="submit" value="SUBMIT">
</form>

submit.php

<?php
if(isset($_POST['submit']) && !empty($_POST['submit'])){
  if(isset($_POST['g-recaptcha-response']) && !empty($_POST['g-recaptcha-response'])){
    //your site secret key
    $secret = 'InsertSiteSecretKey';
    //get verify response data
    $verifyResponse = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret='.$secret.'&response='.$_POST['g-recaptcha-response']);
    $responseData = json_decode($verifyResponse);
    if($responseData->success){
        //contact form submission code goes here

        $succMsg = 'Your contact request have submitted successfully.';
    }else{
        $errMsg = 'Robot verification failed, please try again.';
    }
  }else{
    $errMsg = 'Please click on the reCAPTCHA box.';
  }
}
?>

I have found this reference and full tutorial from here - Using new Google reCAPTCHA with PHP

Solution 5

I liked Levit's answer and ended up using it. But I just wanted to point out, just in case, that there is an official Google PHP library for new reCAPTCHA: https://github.com/google/recaptcha

The latest version (right now 1.1.2) supports Composer and contains an example that you can run to see if you have configured everything correctly.

Below you can see part of the example that comes with this official library (with my minor modifications for clarity):

// Make the call to verify the response and also pass the user's IP address
    $resp = $recaptcha->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);

    if ($resp->isSuccess()) {
// If the response is a success, that's it!
        ?>
        <h2>Success!</h2>
        <p>That's it. Everything is working. Go integrate this into your real project.</p>
        <p><a href="/">Try again</a></p>
        <?php
    } else {
// If it's not successful, then one or more error codes will be returned.
        ?>
        <h2>Something went wrong</h2>
        <p>The following error was returned: <?php
            foreach ($resp->getErrorCodes() as $code) {
                echo '<tt>' , $code , '</tt> ';
            }
            ?></p>
        <p>Check the error code reference at <tt><a href="https://developers.google.com/recaptcha/docs/verify#error-code-reference">https://developers.google.com/recaptcha/docs/verify#error-code-reference</a></tt>.
        <p><strong>Note:</strong> Error code <tt>missing-input-response</tt> may mean the user just didn't complete the reCAPTCHA.</p>
        <p><a href="/">Try again</a></p>
    <?php
    }

Hope it helps someone.

Share:
135,045
Moatez
Author by

Moatez

Updated on July 05, 2022

Comments

  • Moatez
    Moatez almost 2 years

    I've just set up the new google recaptcha with checkbox, it's working fine on front end, however I don't know how to handle it on server side using PHP. I've tried to use the old code below but the form is sent even if the captcha is not valid.

    require_once('recaptchalib.php');
    $privatekey = "my key";
    $resp = recaptcha_check_answer ($privatekey,
            $_SERVER["REMOTE_ADDR"],
            $_POST["recaptcha_challenge_field"],
            $_POST["recaptcha_response_field"]);
    
    if (!$resp->is_valid) {
     $errCapt='<p style="color:#D6012C ">The CAPTCHA Code wasnot entered correctly.</p>';}
    
  • Curtis Mattoon
    Curtis Mattoon about 9 years
    This is because you're not passing true as the 2nd parameter to json_decode. By default, json_decode returns an object (jsOn), but passing true will allow it to return an array.
  • Levite
    Levite about 9 years
    If you need the old array syntax, change the $data = ... and $options = ... declarations to this: $data = array('secret' => '[YOUR SECRET KEY]', 'response' => $_POST['g-recaptcha-response'], 'remoteip' => $_SERVER['REMOTE_ADDR']); $options = array( 'http' => array( 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 'method' => 'POST', 'content' => http_build_query($data) ) );
  • sffc
    sffc over 8 years
    The reCAPTCHA form field value is being concatenated directly into the verify URL. An attacker could use that to inject arbitrary form parameters on the verify request, potentially circumventing the CAPTCHA. To solve this problem, sanitize the captcha variable like this: urlencode($captcha)
  • Paul
    Paul over 8 years
    this code does not encode the parameters which can be used by attackers to break your code.
  • ͢bts
    ͢bts about 8 years
    The data should be POSTed as specified in the docs. This code uses a GET method. I'd go with a variation on Levit's answer.
  • Westy92
    Westy92 over 7 years
    You can save a request to the server and bail ahead of time: if (empty($_POST['g-recaptcha-response'])) { return false; }
  • Varga Tamas
    Varga Tamas over 7 years
    https urls are actually encrypted, so the private key is not exposed even when using a GET request, see: stackoverflow.com/questions/499591/are-https-urls-encrypted
  • Gregory R.
    Gregory R. over 7 years
    This is a good working solution if you are unable to use the file_get_contents() due to 'allow_url_fopen' being disabled in php.ini. Works for me. +1.
  • Sablefoste
    Sablefoste about 7 years
    Please note, for debugging purposes, you can only call isValid() once. If called a second time, it will return false. Found this out the hard way with an echo statement...
  • chris
    chris over 6 years
    None of the function used throw exceptions so the try{}catch() is pointless
  • Joe
    Joe about 6 years
    Worked well for me. Except (in index.html) on the form line: action="form.php", it should be action="verify.php".
  • Nicolas
    Nicolas almost 6 years
    "In the example above..." please note that the StackExchange platform sorts answers according to their ranking by votes. The example above is clearly not the one you were referring to.
  • Brian C
    Brian C over 5 years
    Even though https is encrypted, the private key could still be exposed in logs or other mechanisms. Of course, it's extremely unlikely Google's logs will be compromised, but it's always good to be a little paranoid when there's no/negligible expense.
  • Levite
    Levite over 5 years
    @chris: When the decoded $result does not have the member success (e.g. because it is null), this should apply (problems with network connections, blocked google (firewall policies), etc. come to mind), possibly rare, but reasonable to handle I would argue).
  • Levite
    Levite over 5 years
    @BrianC: Definitely. Well it is actually discouraged sending sensitive data via GET in general (even when using https), I added a good (imho) SO answer about the topic.
  • Mohammed Kumel
    Mohammed Kumel over 5 years
    This codes works on my Shared hosting & not on VPS server. Are there any requirements linked to server?
  • John Contarino
    John Contarino over 5 years
    Straightforward and simple, and uses POST as required by Google
  • pttsky
    pttsky about 3 years
    gotta love that response from server