Steve
Steve

Reputation: 5545

New Google Recaptcha with ASP.Net

I am attempting to get the new Google reCaptcha working in my ASP.NET project and I am having problems getting it to be the new one "I'm not a robot".

I had the old one in there and after doing much research on the developers.google.com web site, everything looks the same (they even point me to a download of the same dll - 1.0.5). So, I got the new keys and put them in and it works but it looks just like the old reCaptcha.

Has anyone gotten the new one to work with their ASP.Net? What am I missing?

EDIT:

So playing around in a test app and searching some other web sites I found that if I create a page like this:

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>reCAPTCHA demo: Simple page</title>
     <script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body>
    <form id="form1" runat="server" action="?" method="POST">
    <div>
    <div class="g-recaptcha" data-sitekey="My Public Key"></div>
      <br/>
        <asp:Button ID="Button1" runat="server" Text="Submit" />

    </div>
    </form>
</body>
</html>

And then in my code-behind (Button1_Click), I do this:

Dim Success As Boolean
Dim recaptchaResponse As String = request.Form("g-recaptcha-response")
If Not String.IsNullOrEmpty(recaptchaResponse) Then
    Success = True
Else
    Success = False
End If

The recaptchaResponse will either be empty or filled in depending on if they are a bot or not. The issue is, I now need to take this response and send it to google with my private key so I can verify that the response was not provided by a bot, in my code-behind, but I cannot figure out how. I tried this (in place of Success = True):

Dim client As New System.Net.Http.HttpClient()
client.BaseAddress = New Uri("https://www.google.com/recaptcha/")
client.DefaultRequestHeaders.Accept.Clear()
client.DefaultRequestHeaders.Accept.Add(New Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"))

Dim response As Net.Http.HttpResponseMessage = Await client.GetAsync("api/siteverify?secret=My Private key&response=" + recaptchaResponse)
If (response.IsSuccessStatusCode) Then
    Dim CaptchResponse As ReCaptchaModel = Await response.Content.ReadAsAsync(Of ReCaptchaModel)()
    Success = CaptchResponse.success
Else
    Success = False
End If

But, I could not figure out how to get the async stuff working and I cannot find anything on what ReCaptchaModel is, so I found another way to call a web service and get a json response and tried this instead:

Dim request As Net.WebRequest = Net.WebRequest.Create("https://www.google.com/recaptcha/")
Dim Data As String = "api/siteverify?secret=My Private Key&response=" + recaptchaResponse
request.Method = "POST"
request.ContentType = "application/json; charset=utf-8"
Dim postData As String = "{""data"":""" + Data + """}"
'get a reference to the request-stream, and write the postData to it
Using s As IO.Stream = request.GetRequestStream()
    Using sw As New IO.StreamWriter(s)
        sw.Write(postData)
    End Using
End Using
'get response-stream, and use a streamReader to read the content
Using s As IO.Stream = request.GetResponse().GetResponseStream()
    Using sr As New IO.StreamReader(s)
        'decode jsonData with javascript serializer
        Dim jsonData = sr.ReadToEnd()
        Stop
    End Using
End Using

But, this just gives me the content of the web page at https://www.google.com/recaptcha. Not what I want. The Google page isn't very useful and I am stuck on where to go. I need some help either calling the Google verify service or if anyone has found another way to do this from ASP.NET.

Upvotes: 5

Views: 10499

Answers (4)

Shaun Peet
Shaun Peet

Reputation: 21

This adds a few things. It converts the response from Google into a Json object, it adds a timeout on the verification request, and it adds a verification of the hostname (required by Google if sending requests from multiple domains and the domains aren't listed in the Google Admin area).

Imports Newtonsoft.Json
Public Class Google
Public Class ReCaptcha

Private Const secret_key = "YOUR_SECRET_KEY"

Public Shared Function Validate(Request As HttpRequest, hostname As String) As Boolean
  Dim g_captcha_response = Request.Form("g-recaptcha-response")
  If Not String.IsNullOrEmpty(g_captcha_response) Then
    Dim response = ExecuteVerification(g_captcha_response)
    If Not response.StartsWith("ERROR:") Then
      Dim json_obj = JsonConvert.DeserializeObject(Of ValidateResponse)(response)
      If json_obj.success Then
        If json_obj.hostname.ToLower = hostname.ToLower Then Return True
      End If
    End If
  End If
  Return False
End Function

Private Shared Function ExecuteVerification(g_captcha_response As String) As String
  Dim request As Net.WebRequest = Net.WebRequest.Create("https://www.google.com/recaptcha/api/siteverify?secret=" & secret_key & "&response=" & g_captcha_response)
  request.Timeout = 5 * 1000 ' 5 Seconds to avoid getting locked up
  request.Method = "POST"
  request.ContentType = "application/json"
  Try
    Dim byteArray As Byte() = Encoding.UTF8.GetBytes("")
    request.ContentLength = byteArray.Length
    Dim dataStream As Stream = request.GetRequestStream()
    dataStream.Write(byteArray, 0, byteArray.Length)
    dataStream.Close()
    Dim response As Net.WebResponse = request.GetResponse()
    dataStream = response.GetResponseStream()
    Dim reader As New StreamReader(dataStream)
    Dim responseFromServer As String = reader.ReadToEnd()
    reader.Close()
    response.Close()
    Return responseFromServer
  Catch ex As Exception
    Return "ERROR: " & ex.Message
  End Try
End Function

Public Class ValidateResponse
  Public Property success As Boolean
  Public Property challenge_ts As DateTime
  Public Property hostname As String
  <JsonProperty("error-codes")>
  Public Property error_codes As List(Of String)
End Class

End Class

End Class

So in the button's Click event, just call:

If Google.ReCaptcha.Validate(Request, Request.Url.Host) Then
    ' good to go
Else
    ' validation failed
End If

Upvotes: 0

jonrsharpe
jonrsharpe

Reputation: 122154

I took a slightly different approach, using the data-callback option and a Session parameter. The following sits within the MainContent block of the .aspx file:

<asp:ScriptManager ID="scrEnablePage" EnablePageMethods="true" runat="server" />
<asp:Panel ID="pnlCaptcha" runat="server" Visible="true">
    <div class="g-recaptcha" 
        data-sitekey='<asp:Literal ID="litKey" runat="server" Text="<%$ AppSettings:recaptchaPublicKey%>" />'
        data-callback="handleCaptcha"></div>
</asp:Panel>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<script type="text/javascript">
    function handleCaptcha(e) {
        PageMethods.RecaptchaValid(e);
        location.reload(true);
    }
</script>

Then in the code-behind:

Private Const GoogleUrl As String = "https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}"

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    pnlCaptcha.Visible = Not (Session("VerifiedHuman") = "True")
    ...
End Sub

<System.Web.Services.WebMethod(EnableSession:=True)> _
Public Shared Sub RecaptchaValid(response As String)

    Dim client As New System.Net.WebClient()
    Dim outcome As Dictionary(Of String, String)
    Dim result As String = String.Join(vbCrLf,
                                       {"{", """success"": true", "}"})
    Dim serializer As New System.Web.Script.Serialization.JavaScriptSerializer()

    Dim url As String = String.Format(GoogleUrl,
                                      ConfigurationManager.AppSettings.Get("recaptchaPrivateKey"),
                                      response)

    Try
        result = client.DownloadString(url)
    Catch ex As System.Net.WebException
        Exit Sub  ' Comment out to default to passing
    End Try

    outcome = serializer.Deserialize(Of Dictionary(Of String, String))(result)
    HttpContext.Current.Session("VerifiedHuman") = outcome("success")

End Sub

Now in Page_Load you can check Session("VerifiedHuman") = "True" and update your page controls accordingly, hiding the panel with the Captcha control and showing the other appropriate items.

Note that this takes the keys from Web.config, i.e.

<configuration>
  <appSettings>
    <add key="recaptchaPublicKey" value="..." />
    <add key="recaptchaPrivateKey" value="..." />
    ...
  </appSettings>
  ...
</configuration>

Upvotes: 0

AVenger
AVenger

Reputation: 116

Thank you for sharing this. It worked for me. I went ahead and converted it to C# (since that's what I was using) and added a few things.

  • I changed the validation step. I split the JSON string and evaluated if success was found where it should be.
  • I used the ConfigurationManager to store the ReCaptcha Keys.
  • Finally, I changed it from using a WebRequest to using and HttpClient. This cut the code in half because I don't need to read the stream now.

Feel free to use this code as well.

private static bool IsReCaptchaValid(string response)
{
    if (string.IsNullOrWhiteSpace(response))
    {
        return false;
    }

    var client = new HttpClient();
    string result =
        client.GetStringAsync(string.Format("{0}?secret={1}&response={2}", ConfigurationManager.AppSettings["ReCaptchaValidationLink"],
            ConfigurationManager.AppSettings["ReCaptchaSecretKey"], response)).Result;
    string[] split = result.Split('\"');

    return split[1] == "success";
}

Upvotes: 0

Steve
Steve

Reputation: 5545

I had just about given up when I ran across something unrelated that made me think about it again and in a different way. In my last attempt above, I was attempting to pass the private key and recaptcha response as the data, so I tried it in the create of the WebRequest and it worked. Here is the final solution:

Using the same HTML posted above, I created a function that I can call in the button click event where I check the Page.IsValid and call this function:

Private Function IsGoogleCaptchaValid() As Boolean
    Try
        Dim recaptchaResponse As String = Request.Form("g-recaptcha-response")
        If Not String.IsNullOrEmpty(recaptchaResponse) Then
            Dim request As Net.WebRequest = Net.WebRequest.Create("https://www.google.com/recaptcha/api/siteverify?secret=My Private Key&response=" + recaptchaResponse)
            request.Method = "POST"
            request.ContentType = "application/json; charset=utf-8"
            Dim postData As String = ""

            'get a reference to the request-stream, and write the postData to it
            Using s As IO.Stream = request.GetRequestStream()
                Using sw As New IO.StreamWriter(s)
                    sw.Write(postData)
                End Using
            End Using
            ''get response-stream, and use a streamReader to read the content
            Using s As IO.Stream = request.GetResponse().GetResponseStream()
                Using sr As New IO.StreamReader(s)
                    'decode jsonData with javascript serializer
                    Dim jsonData = sr.ReadToEnd()
                    If jsonData = "{" & vbLf & "  ""success"": true" & vbLf & "}" Then
                        Return True
                    End If
                End Using
            End Using
        End If
    Catch ex As Exception
        'Dont show the error
    End Try
    Return False
End Function

I'm sure there are improvements to be made to the code, but it works. I couldn't see adding references to some JSON libraries for reading one thing I just check the string.

Upvotes: 8

Related Questions