Last active
August 15, 2024 10:50
-
-
Save adnanalbeda/69b9238a6176e4e59c283eb1930e6076 to your computer and use it in GitHub Desktop.
Google ReCaptcha V3 - ASP API - C#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Google.ReCaptcha.V3; | |
public class CaptchaRequestException : Exception | |
{ | |
public CaptchaRequestException() | |
{ | |
} | |
public CaptchaRequestException(string message) | |
: base(message) | |
{ | |
} | |
public CaptchaRequestException(string message, Exception inner) | |
: base(message, inner) | |
{ | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Extensions.DependencyInjection; | |
namespace Google.ReCaptcha.V3; | |
public static class Extensions | |
{ | |
public static IServiceCollection AddGoogleRecaptchaV3(this IServiceCollection services) | |
{ | |
services.AddHttpClient<IGoogleRecaptchaV3Client, GoogleRecaptchaV3Client>(); | |
services.AddTransient<IGoogleRecaptchaV3Client, GoogleRecaptchaV3Client>(); | |
return services; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Runtime.Serialization.Json; | |
using System.Web; | |
namespace Google.ReCaptcha.V3; | |
public class GoogleRecaptchaV3Client : IGoogleRecaptchaV3Client | |
{ | |
private readonly HttpClient _httpClient; | |
public RequestModel? Request { get; set; } | |
public ResponseModel? Response { get; set; } | |
public HttpRequestException? HttpReqException { get; set; } | |
public Exception? GeneralException { get; set; } | |
public GoogleRecaptchaV3Client(HttpClient httpClient) | |
{ | |
_httpClient = httpClient; | |
} | |
public void InitializeRequest(RequestModel request) | |
{ | |
Request = request; | |
} | |
public async Task<bool> Execute() | |
{ | |
// Notes on error handling: | |
// Google will pass back a 200 Status Ok response if no network or server errors occur. | |
// If there are errors in on the "business" level, they will be coded in an array; | |
// CaptchaRequestException is for these types of errors. | |
// CaptchaRequestException and multiple catches are used to help seperate the concerns of | |
// a) an HttpRequest 400+ status code | |
// b) an error at the "business" level | |
// c) an unpredicted error that can only be handled generically. | |
// It might be worthwhile to implement a "user error message" property in this class so the | |
// calling procedure can decide what, if anything besides a server error, to return to the | |
// client and any client handling from there on. | |
try | |
{ | |
ArgumentNullException.ThrowIfNull(Request); | |
//Don't to forget to invoke any loggers in the logic below. | |
//formulate request | |
string builtURL = Request.path + '?' + HttpUtility.UrlPathEncode($"secret={Request.secret}&response={Request.response}&remoteip={Request.remoteip}"); | |
StringContent content = new StringContent(builtURL); | |
Console.WriteLine($"Sent Request {builtURL}"); | |
//send request, await. | |
HttpResponseMessage response = await _httpClient.PostAsync(builtURL, null); | |
response.EnsureSuccessStatusCode(); | |
//read response | |
byte[] res = await response.Content.ReadAsByteArrayAsync(); | |
string logres = await response.Content.ReadAsStringAsync(); | |
Console.WriteLine($"Retrieved Response: {logres}"); | |
//Serialize into GReponse type | |
using (MemoryStream ms = new MemoryStream(res)) | |
{ | |
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(ResponseModel)); | |
Response = (ResponseModel)serializer.ReadObject(ms)!; | |
} | |
//check if business success | |
if (!Response.success) | |
{ | |
throw new CaptchaRequestException(); | |
} | |
//return bool. | |
return true; //response.IsSuccessStatusCode; <- don't need this. EnsureSuccessStatusCode is now in play. | |
} | |
catch (HttpRequestException hre) | |
{ | |
//handle http error code. | |
HttpReqException = hre; | |
//invoke logger accordingly | |
//only returning bool. It is ultimately up to the calling procedure | |
//to decide what data it wants from the Service. | |
return false; | |
} | |
catch (CaptchaRequestException ex) | |
{ | |
//Business-level error... values are accessible in error-codes array. | |
//this catch block mainly serves for logging purposes. | |
/* Here are the possible "business" level codes: | |
missing-input-secret The secret parameter is missing. | |
invalid-input-secret The secret parameter is invalid or malformed. | |
missing-input-response The response parameter is missing. | |
invalid-input-response The response parameter is invalid or malformed. | |
bad-request The request is invalid or malformed. | |
timeout-or-duplicate The response is no longer valid: either is too old or has been used previously. | |
*/ | |
//invoke logger accordingly | |
//only returning bool. It is ultimately up to the calling procedure | |
//to decide what data it wants from the Service. | |
return false; | |
} | |
catch (Exception ex) | |
{ | |
// Generic unpredictable error | |
GeneralException = ex; | |
// invoke logger accordingly | |
//only returning bool. It is ultimately up to the calling procedure | |
//to decide what data it wants from the Service. | |
return false; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Google.ReCaptcha.V3; | |
/// <summary> | |
/// Register with: `AddHttpClient` | |
/// </summary> | |
public interface IGoogleRecaptchaV3Client | |
{ | |
RequestModel? Request { get; set; } | |
ResponseModel? Response { get; set; } | |
void InitializeRequest(RequestModel request); | |
Task<bool> Execute(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// const site_key = ""; | |
// var script = document.createElement("script"); | |
// script.type = "text/javascript"; | |
// script.src = | |
// "https://www.google.com/recaptcha/api.js?render=" + site_key; | |
// document.body.appendChild(script); | |
// const getReCaptchaToken = (action = "submit") => grecaptcha.ready(() => grecaptcha.execute(site_key, { action } )); | |
const loadReCaptcha = (site_key) => { | |
if (!site_key || typeof site_key !== "string" || site_key.length < 10) throw new Error("Invalid site_key."); | |
if (grecaptcha) return; | |
const captcha = { | |
key: site_key, | |
loaded:false, | |
getToken: (action = "submit") => grecaptcha.ready(() => grecaptcha.execute(site_key, { action } )) | |
} | |
var script = document.createElement("script"); | |
script.type = "text/javascript"; | |
script.src = `https://www.google.com/recaptcha/api.js?render=${site_key}`; | |
script.addEventListener("load", () => {captcha.loaded = true;}) | |
document.body.appendChild(script); | |
return captcha; | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Google.ReCaptcha.V3; | |
public class RequestModel | |
{ | |
// CONFIG:CAPTCHA_URL :: `https://www.google.com/recaptcha/api/siteverify` | |
/// <summary> | |
/// Fill From: <br/> | |
/// CONFIG:CAPTCHA_URL :: `https://www.google.com/recaptcha/api/siteverify` | |
/// </summary> | |
public required string path { get; set; } | |
// CONFIG:CAPTCHA_SECRET | |
/// <summary> | |
/// Fill From: <br/> | |
/// CONFIG:CAPTCHA_SECRET | |
/// </summary> | |
public required string secret { get; set; } | |
// CLIENT_POST_DATA:RecaptchaToken | |
/// <summary> | |
/// Fill From: <br/> | |
/// CLIENT_POST_DATA:RecaptchaToken | |
/// </summary> | |
public required string response { get; set; } | |
// HttpContext.Connection.RemoteIpAddress.ToString() | |
/// <summary> | |
/// Fill From: <br/> | |
/// HttpContext.Connection.RemoteIpAddress.ToString() | |
/// </summary> | |
public required string remoteip { get; set; } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Runtime.Serialization; | |
namespace Google.ReCaptcha.V3; | |
// Google's response property naming is | |
// embarrassingly inconsistent, that's why we have to | |
// use DataContract and DataMember attributes, | |
// so we can bind the class from properties that have | |
// naming where a C# variable by that name would be | |
// against the language specifications... (i.e., '-'). | |
[DataContract] | |
public class ResponseModel | |
{ | |
[DataMember] | |
public bool success { get; set; } | |
[DataMember] | |
public double score { get; set; } | |
[DataMember] | |
public string challenge_ts { get; set; } // or datetime | |
[DataMember] | |
public string hostname { get; set; } | |
[DataMember] | |
public string action { get; set; } | |
//Could create a child object for | |
//error-codes | |
[DataMember(Name = "error-codes")] | |
public string[] error_codes { get; set; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment