├── .gitignore ├── BlazorReCaptchaSample.sln ├── BlazorServerEdition └── Server │ ├── App.razor │ ├── BlazorReCaptchaSample.Server.csproj │ ├── Pages │ ├── Step1.razor │ ├── Step2.razor │ └── _Host.cshtml │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── ReCAPTCHA.razor │ ├── SampleAPI.cs │ ├── Startup.cs │ ├── _Imports.razor │ ├── appsettings.json │ ├── reCAPTCHAVerificationOptions.cs │ ├── reCAPTCHAVerificationResponse.cs │ └── wwwroot │ ├── favicon.ico │ ├── scripts │ ├── script.js │ ├── script.js.map │ └── script.ts │ └── style.css ├── BlazorWasmEdition ├── Client │ ├── App.razor │ ├── BlazorReCaptchaSample.Client.csproj │ ├── Pages │ │ ├── Step1.razor │ │ └── Step2.razor │ ├── Program.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ └── launchSettings.json │ ├── ReCAPTCHA.razor │ ├── SampleSite.Components.csproj │ ├── _Imports.razor │ └── wwwroot │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── scripts │ │ ├── script.js │ │ ├── script.js.map │ │ └── script.ts │ │ └── style.css ├── Host │ ├── BlazorReCaptchaSample.Host.csproj │ ├── Controllers │ │ └── SampleAPIController.cs │ ├── Program.cs │ ├── Properties │ │ ├── PublishProfiles │ │ │ └── blazor-recaptcha-sample - Web Deploy.pubxml │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appSettings.json │ ├── reCAPTCHAVerificationOptions.cs │ └── reCAPTCHAVerificationResponse.cs └── Shared │ ├── BlazorReCaptchaSample.Shared.csproj │ └── SampleAPIArgs.cs ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .vs/ 4 | *.user 5 | *.suo 6 | appSettings.Development.json 7 | -------------------------------------------------------------------------------- /BlazorReCaptchaSample.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.0.0 4 | MinimumVisualStudioVersion = 16.0.0.0 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorReCaptchaSample.Host", "BlazorWasmEdition\Host\BlazorReCaptchaSample.Host.csproj", "{6E1E976D-315B-461F-8A47-DC6C0D69FEA8}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorReCaptchaSample.Client", "BlazorWasmEdition\Client\BlazorReCaptchaSample.Client.csproj", "{E4F5C735-BB90-4C44-8B6D-B7723A985B38}" 8 | EndProject 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorReCaptchaSample.Shared", "BlazorWasmEdition\Shared\BlazorReCaptchaSample.Shared.csproj", "{0341B023-80E5-4FEA-B58E-CD70D1D432EC}" 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "- README -", "- README -", "{DA112AC4-9D16-4397-BD7D-570BEDAD7142}" 12 | ProjectSection(SolutionItems) = preProject 13 | LICENSE = LICENSE 14 | README.md = README.md 15 | EndProjectSection 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blazor WebAssmbly App (Client-Side Blazor) Edition", "Blazor WebAssmbly App (Client-Side Blazor) Edition", "{3E8808D4-3993-4261-ADAD-6A4D1493FB9F}" 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blazor Server App (Server-Side Blazor) Edition", "Blazor Server App (Server-Side Blazor) Edition", "{466F4BCB-AC3D-48D5-837C-8D00F4C56C7F}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorReCaptchaSample.Server", "BlazorServerEdition\Server\BlazorReCaptchaSample.Server.csproj", "{ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Debug|x64 = Debug|x64 27 | Debug|x86 = Debug|x86 28 | Release|Any CPU = Release|Any CPU 29 | Release|x64 = Release|x64 30 | Release|x86 = Release|x86 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8}.Debug|x64.ActiveCfg = Debug|Any CPU 36 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8}.Debug|x64.Build.0 = Debug|Any CPU 37 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8}.Debug|x86.ActiveCfg = Debug|Any CPU 38 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8}.Debug|x86.Build.0 = Debug|Any CPU 39 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8}.Release|x64.ActiveCfg = Release|Any CPU 42 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8}.Release|x64.Build.0 = Release|Any CPU 43 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8}.Release|x86.ActiveCfg = Release|Any CPU 44 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8}.Release|x86.Build.0 = Release|Any CPU 45 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38}.Debug|x64.ActiveCfg = Debug|Any CPU 48 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38}.Debug|x64.Build.0 = Debug|Any CPU 49 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38}.Debug|x86.ActiveCfg = Debug|Any CPU 50 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38}.Debug|x86.Build.0 = Debug|Any CPU 51 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38}.Release|x64.ActiveCfg = Release|Any CPU 54 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38}.Release|x64.Build.0 = Release|Any CPU 55 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38}.Release|x86.ActiveCfg = Release|Any CPU 56 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38}.Release|x86.Build.0 = Release|Any CPU 57 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 58 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC}.Debug|Any CPU.Build.0 = Debug|Any CPU 59 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC}.Debug|x64.ActiveCfg = Debug|Any CPU 60 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC}.Debug|x64.Build.0 = Debug|Any CPU 61 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC}.Debug|x86.ActiveCfg = Debug|Any CPU 62 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC}.Debug|x86.Build.0 = Debug|Any CPU 63 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC}.Release|Any CPU.Build.0 = Release|Any CPU 65 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC}.Release|x64.ActiveCfg = Release|Any CPU 66 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC}.Release|x64.Build.0 = Release|Any CPU 67 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC}.Release|x86.ActiveCfg = Release|Any CPU 68 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC}.Release|x86.Build.0 = Release|Any CPU 69 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 70 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}.Debug|Any CPU.Build.0 = Debug|Any CPU 71 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}.Debug|x64.ActiveCfg = Debug|Any CPU 72 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}.Debug|x64.Build.0 = Debug|Any CPU 73 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}.Debug|x86.ActiveCfg = Debug|Any CPU 74 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}.Debug|x86.Build.0 = Debug|Any CPU 75 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}.Release|Any CPU.ActiveCfg = Release|Any CPU 76 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}.Release|Any CPU.Build.0 = Release|Any CPU 77 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}.Release|x64.ActiveCfg = Release|Any CPU 78 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}.Release|x64.Build.0 = Release|Any CPU 79 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}.Release|x86.ActiveCfg = Release|Any CPU 80 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47}.Release|x86.Build.0 = Release|Any CPU 81 | EndGlobalSection 82 | GlobalSection(SolutionProperties) = preSolution 83 | HideSolutionNode = FALSE 84 | EndGlobalSection 85 | GlobalSection(NestedProjects) = preSolution 86 | {6E1E976D-315B-461F-8A47-DC6C0D69FEA8} = {3E8808D4-3993-4261-ADAD-6A4D1493FB9F} 87 | {E4F5C735-BB90-4C44-8B6D-B7723A985B38} = {3E8808D4-3993-4261-ADAD-6A4D1493FB9F} 88 | {0341B023-80E5-4FEA-B58E-CD70D1D432EC} = {3E8808D4-3993-4261-ADAD-6A4D1493FB9F} 89 | {ABC16D06-9390-4A40-AE8A-BA6DEBD70A47} = {466F4BCB-AC3D-48D5-837C-8D00F4C56C7F} 90 | EndGlobalSection 91 | GlobalSection(ExtensibilityGlobals) = postSolution 92 | SolutionGuid = {12616DCF-E921-48CA-A211-A262FA10D5E9} 93 | EndGlobalSection 94 | EndGlobal 95 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 |

Sorry, there's nothing at this address.

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/BlazorReCaptchaSample.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | all 11 | runtime; build; native; contentfiles; analyzers; buildtransitive 12 | 13 | 14 | 15 | 16 | ES2015 17 | React 18 | None 19 | True 20 | True 21 | True 22 | 23 | 24 | False 25 | True 26 | True 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/Pages/Step1.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @inject IJSRuntime JS 3 | @inject NavigationManager Navigation 4 | @inject SampleAPI SampleAPI 5 | 6 |
7 | 8 |
9 | 10 |
11 | 14 |
15 | 16 | @code { 17 | 18 | private ReCAPTCHA reCAPTCHAComponent; 19 | 20 | private bool ValidReCAPTCHA = false; 21 | 22 | private bool ServerVerificatiing = false; 23 | 24 | private bool DisablePostButton => !ValidReCAPTCHA || ServerVerificatiing; 25 | 26 | private void OnSuccess() 27 | { 28 | ValidReCAPTCHA = true; 29 | } 30 | 31 | private void OnExpired() 32 | { 33 | ValidReCAPTCHA = false; 34 | } 35 | 36 | private async Task OnClickPost() 37 | { 38 | if (ValidReCAPTCHA) 39 | { 40 | var response = await reCAPTCHAComponent.GetResponseAsync(); 41 | try 42 | { 43 | ServerVerificatiing = true; 44 | StateHasChanged(); 45 | var result = await SampleAPI.Post(response); 46 | if (result.Success) 47 | { 48 | Navigation.NavigateTo("/valid"); 49 | } 50 | else 51 | { 52 | await JS.InvokeAsync("alert", string.Join(", ", result.ErrorCodes)); 53 | ServerVerificatiing = false; 54 | StateHasChanged(); 55 | } 56 | } 57 | catch (HttpRequestException e) 58 | { 59 | await JS.InvokeAsync("alert", e.Message); 60 | ServerVerificatiing = false; 61 | StateHasChanged(); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/Pages/Step2.razor: -------------------------------------------------------------------------------- 1 | @page "/valid" 2 | @inject NavigationManager Navigation 3 | 4 |
5 | Your post is valid. 6 |
7 | 8 |
9 | 10 |
11 | 12 | @code { 13 | 14 | private void OnClickGoBack() 15 | { 16 | Navigation.NavigateTo("/"); 17 | } 18 | } -------------------------------------------------------------------------------- /BlazorServerEdition/Server/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @namespace BlazorReCaptchaSample.Server.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @{ 5 | Layout = null; 6 | } 7 | 8 | 9 | 10 | 11 | 12 | 13 | Blazor reCAPTCHA Sample Site 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | An error has occurred. This application may no longer respond until reloaded. 28 | 29 | 30 | An unhandled exception has occurred. See browser dev tools for details. 31 | 32 | Reload 33 | 🗙 34 |
35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace BlazorReCaptchaSample.Server 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:54117", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "BlazorReCaptchaSample.Server": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/ReCAPTCHA.razor: -------------------------------------------------------------------------------- 1 | @using System.ComponentModel 2 | @inject IJSRuntime JS 3 | 4 |
5 | 6 | @code { 7 | 8 | [Parameter] 9 | public string SiteKey { get; set; } 10 | 11 | [Parameter] 12 | public EventCallback OnSuccess { get; set; } 13 | 14 | [Parameter] 15 | public EventCallback OnExpired { get; set; } 16 | 17 | private string UniqueId = Guid.NewGuid().ToString(); 18 | 19 | private int WidgetId; 20 | 21 | protected override async Task OnAfterRenderAsync(bool firstRender) 22 | { 23 | if (firstRender) 24 | { 25 | await JS.InvokeAsync("My.reCAPTCHA.init"); 26 | this.WidgetId = await JS.InvokeAsync("My.reCAPTCHA.render", DotNetObjectReference.Create(this), UniqueId, SiteKey); 27 | } 28 | } 29 | 30 | [JSInvokable, EditorBrowsable(EditorBrowsableState.Never)] 31 | public void CallbackOnSuccess(string response) 32 | { 33 | if (OnSuccess.HasDelegate) 34 | { 35 | OnSuccess.InvokeAsync(response); 36 | } 37 | } 38 | 39 | [JSInvokable, EditorBrowsable(EditorBrowsableState.Never)] 40 | public void CallbackOnExpired() 41 | { 42 | if (OnExpired.HasDelegate) 43 | { 44 | OnExpired.InvokeAsync(null); 45 | } 46 | } 47 | 48 | public ValueTask GetResponseAsync() 49 | { 50 | return JS.InvokeAsync("My.reCAPTCHA.getResponse", WidgetId); 51 | } 52 | } -------------------------------------------------------------------------------- /BlazorServerEdition/Server/SampleAPI.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Options; 6 | 7 | namespace BlazorReCaptchaSample.Server 8 | { 9 | public class SampleAPI 10 | { 11 | private IHttpClientFactory HttpClientFactory { get; } 12 | 13 | private IOptionsMonitor reCAPTCHAVerificationOptions { get; } 14 | 15 | public SampleAPI(IHttpClientFactory httpClientFactory, IOptionsMonitor reCAPTCHAVerificationOptions) 16 | { 17 | this.HttpClientFactory = httpClientFactory; 18 | this.reCAPTCHAVerificationOptions = reCAPTCHAVerificationOptions; 19 | } 20 | 21 | public async Task<(bool Success, string[] ErrorCodes)> Post(string reCAPTCHAResponse) 22 | { 23 | var url = "https://www.google.com/recaptcha/api/siteverify"; 24 | var content = new FormUrlEncodedContent(new Dictionary 25 | { 26 | {"secret", this.reCAPTCHAVerificationOptions.CurrentValue.Secret}, 27 | {"response", reCAPTCHAResponse} 28 | }); 29 | 30 | var httpClient = this.HttpClientFactory.CreateClient(); 31 | var response = await httpClient.PostAsync(url, content); 32 | response.EnsureSuccessStatusCode(); 33 | 34 | var verificationResponse = await response.Content.ReadAsAsync(); 35 | if (verificationResponse.Success) return (Success: true, ErrorCodes: new string[0]); 36 | 37 | return ( 38 | Success: false, 39 | ErrorCodes: verificationResponse.ErrorCodes.Select(err => err.Replace('-', ' ')).ToArray()); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace BlazorReCaptchaSample.Server 8 | { 9 | public class Startup 10 | { 11 | public Startup(IConfiguration configuration) 12 | { 13 | Configuration = configuration; 14 | } 15 | 16 | public IConfiguration Configuration { get; } 17 | 18 | // This method gets called by the runtime. Use this method to add services to the container. 19 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.Configure(Configuration.GetSection("reCAPTCHA")); 23 | services.AddTransient(); 24 | services.AddHttpClient(); 25 | 26 | services.AddRazorPages(); 27 | services.AddServerSideBlazor(); 28 | } 29 | 30 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 31 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 32 | { 33 | if (env.IsDevelopment()) 34 | { 35 | app.UseDeveloperExceptionPage(); 36 | } 37 | else 38 | { 39 | app.UseExceptionHandler("/Error"); 40 | } 41 | 42 | app.UseStaticFiles(); 43 | 44 | app.UseRouting(); 45 | 46 | app.UseEndpoints(endpoints => 47 | { 48 | endpoints.MapBlazorHub(); 49 | endpoints.MapFallbackToPage("/_Host"); 50 | }); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Authorization 3 | @using Microsoft.AspNetCore.Components.Authorization 4 | @using Microsoft.AspNetCore.Components.Forms 5 | @using Microsoft.AspNetCore.Components.Routing 6 | @using Microsoft.AspNetCore.Components.Web 7 | @using Microsoft.JSInterop 8 | @using BlazorReCaptchaSample.Server 9 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "reCAPTCHA": { 3 | "Secret": "" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft": "Warning", 9 | "Microsoft.Hosting.Lifetime": "Information" 10 | } 11 | }, 12 | "AllowedHosts": "*" 13 | } 14 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/reCAPTCHAVerificationOptions.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorReCaptchaSample.Server 2 | { 3 | public class reCAPTCHAVerificationOptions 4 | { 5 | public string Secret { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/reCAPTCHAVerificationResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace BlazorReCaptchaSample.Server 5 | { 6 | public class reCAPTCHAVerificationResponse 7 | { 8 | public bool Success { get; set; } 9 | 10 | // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ) 11 | [JsonPropertyName("challenge_ts")] 12 | public DateTimeOffset ChallengeTimestamp { get; set; } 13 | 14 | // the hostname of the site where the reCAPTCHA was solved 15 | public string Hostname { get; set; } 16 | 17 | [JsonPropertyName("error-codes")] 18 | public string[] ErrorCodes { get; set; } = new string[0]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BlazorServerEdition/Server/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sample-by-jsakamoto/Blazor-UseGoogleReCAPTCHA/2cdabb630403350f6cc6e013e1c62e924e266be6/BlazorServerEdition/Server/wwwroot/favicon.ico -------------------------------------------------------------------------------- /BlazorServerEdition/Server/wwwroot/scripts/script.js: -------------------------------------------------------------------------------- 1 | var My; 2 | (function (My) { 3 | var reCAPTCHA; 4 | (function (reCAPTCHA) { 5 | let scriptLoaded = null; 6 | function waitScriptLoaded(resolve) { 7 | if (typeof (grecaptcha) !== 'undefined' && typeof (grecaptcha.render) !== 'undefined') 8 | resolve(); 9 | else 10 | setTimeout(() => waitScriptLoaded(resolve), 100); 11 | } 12 | function init() { 13 | const scripts = Array.from(document.getElementsByTagName('script')); 14 | if (!scripts.some(s => (s.src || '').startsWith('https://www.google.com/recaptcha/api.js'))) { 15 | const script = document.createElement('script'); 16 | script.src = 'https://www.google.com/recaptcha/api.js?render=explicit'; 17 | script.async = true; 18 | script.defer = true; 19 | document.head.appendChild(script); 20 | } 21 | if (scriptLoaded === null) 22 | scriptLoaded = new Promise(waitScriptLoaded); 23 | return scriptLoaded; 24 | } 25 | reCAPTCHA.init = init; 26 | function render(dotNetObj, selector, siteKey) { 27 | return grecaptcha.render(selector, { 28 | 'sitekey': siteKey, 29 | 'callback': (response) => { dotNetObj.invokeMethodAsync('CallbackOnSuccess', response); }, 30 | 'expired-callback': () => { dotNetObj.invokeMethodAsync('CallbackOnExpired'); } 31 | }); 32 | } 33 | reCAPTCHA.render = render; 34 | function getResponse(widgetId) { 35 | return grecaptcha.getResponse(widgetId); 36 | } 37 | reCAPTCHA.getResponse = getResponse; 38 | })(reCAPTCHA = My.reCAPTCHA || (My.reCAPTCHA = {})); 39 | })(My || (My = {})); 40 | //# sourceMappingURL=script.js.map -------------------------------------------------------------------------------- /BlazorServerEdition/Server/wwwroot/scripts/script.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"script.js","sourceRoot":"","sources":["script.ts"],"names":[],"mappings":"AAEA,IAAU,EAAE,CAmCX;AAnCD,WAAU,EAAE;IAAC,IAAA,SAAS,CAmCrB;IAnCY,WAAA,SAAS;QAClB,IAAI,YAAY,GAAyB,IAAI,CAAC;QAE9C,SAAS,gBAAgB,CAAC,OAAmB;YACzC,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,WAAW,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,WAAW;gBAAE,OAAO,EAAE,CAAC;;gBAC5F,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC;QAC1D,CAAC;QAED,SAAgB,IAAI;YAEhB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC;YACpE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,UAAU,CAAC,yCAAyC,CAAC,CAAC,EAAE;gBACzF,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;gBAChD,MAAM,CAAC,GAAG,GAAG,yDAAyD,CAAC;gBACvE,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;gBACpB,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;gBACpB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;aACrC;YAED,IAAI,YAAY,KAAK,IAAI;gBAAE,YAAY,GAAG,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;YAExE,OAAO,YAAY,CAAC;QACxB,CAAC;QAde,cAAI,OAcnB,CAAA;QAED,SAAgB,MAAM,CAAC,SAAc,EAAE,QAAgB,EAAE,OAAe;YACpE,OAAO,UAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;gBAC/B,SAAS,EAAE,OAAO;gBAClB,UAAU,EAAE,CAAC,QAAgB,EAAE,EAAE,GAAG,SAAS,CAAC,iBAAiB,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACjG,kBAAkB,EAAE,GAAG,EAAE,GAAG,SAAS,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;aAClF,CAAC,CAAC;QACP,CAAC;QANe,gBAAM,SAMrB,CAAA;QAED,SAAgB,WAAW,CAAC,QAAgB;YACxC,OAAO,UAAU,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC;QAFe,qBAAW,cAE1B,CAAA;IACL,CAAC,EAnCY,SAAS,GAAT,YAAS,KAAT,YAAS,QAmCrB;AAAD,CAAC,EAnCS,EAAE,KAAF,EAAE,QAmCX"} -------------------------------------------------------------------------------- /BlazorServerEdition/Server/wwwroot/scripts/script.ts: -------------------------------------------------------------------------------- 1 | declare var grecaptcha: any; 2 | 3 | namespace My.reCAPTCHA { 4 | let scriptLoaded: Promise | null = null; 5 | 6 | function waitScriptLoaded(resolve: () => void) { 7 | if (typeof (grecaptcha) !== 'undefined' && typeof (grecaptcha.render) !== 'undefined') resolve(); 8 | else setTimeout(() => waitScriptLoaded(resolve), 100); 9 | } 10 | 11 | export function init() { 12 | 13 | const scripts = Array.from(document.getElementsByTagName('script')); 14 | if (!scripts.some(s => (s.src || '').startsWith('https://www.google.com/recaptcha/api.js'))) { 15 | const script = document.createElement('script'); 16 | script.src = 'https://www.google.com/recaptcha/api.js?render=explicit'; 17 | script.async = true; 18 | script.defer = true; 19 | document.head.appendChild(script); 20 | } 21 | 22 | if (scriptLoaded === null) scriptLoaded = new Promise(waitScriptLoaded); 23 | 24 | return scriptLoaded; 25 | } 26 | 27 | export function render(dotNetObj: any, selector: string, siteKey: string): number { 28 | return grecaptcha.render(selector, { 29 | 'sitekey': siteKey, 30 | 'callback': (response: string) => { dotNetObj.invokeMethodAsync('CallbackOnSuccess', response); }, 31 | 'expired-callback': () => { dotNetObj.invokeMethodAsync('CallbackOnExpired'); } 32 | }); 33 | } 34 | 35 | export function getResponse(widgetId: number): string { 36 | return grecaptcha.getResponse(widgetId); 37 | } 38 | } -------------------------------------------------------------------------------- /BlazorServerEdition/Server/wwwroot/style.css: -------------------------------------------------------------------------------- 1 | body, button { 2 | font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; 3 | font-size: 18px; 4 | } 5 | 6 | .section { 7 | margin: 16px; 8 | } 9 | 10 | button { 11 | color: #fff; 12 | border: none; 13 | padding: 10px 40px; 14 | background-color: darkblue; 15 | border-radius: 4px; 16 | height: 40px; 17 | transition: all linear 0.2s; 18 | } 19 | 20 | button:hover { 21 | background-color: rgb(100,40,230); 22 | } 23 | 24 | button[disabled] { 25 | background-color: silver; 26 | } 27 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 |

Sorry, there's nothing at this address.

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/BlazorReCaptchaSample.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | 3.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ES2015 25 | React 26 | None 27 | True 28 | True 29 | True 30 | 31 | 32 | False 33 | True 34 | True 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/Pages/Step1.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @inject IJSRuntime JS 3 | @inject NavigationManager Navigation 4 | @inject HttpClient Http 5 | 6 |
7 | 8 |
9 | 10 |
11 | 14 |
15 | 16 | @code { 17 | 18 | private ReCAPTCHA reCAPTCHAComponent; 19 | 20 | private bool ValidReCAPTCHA = false; 21 | 22 | private bool ServerVerificatiing = false; 23 | 24 | private bool DisablePostButton => !ValidReCAPTCHA || ServerVerificatiing; 25 | 26 | private void OnSuccess() 27 | { 28 | ValidReCAPTCHA = true; 29 | } 30 | 31 | private void OnExpired() 32 | { 33 | ValidReCAPTCHA = false; 34 | } 35 | 36 | private async Task OnClickPost() 37 | { 38 | if (ValidReCAPTCHA) 39 | { 40 | var response = await reCAPTCHAComponent.GetResponseAsync(); 41 | try 42 | { 43 | ServerVerificatiing = true; 44 | StateHasChanged(); 45 | await Http.PostAsJsonAsync("/api/sample", new SampleAPIArgs { reCAPTCHAResponse = response }); 46 | Navigation.NavigateTo("/valid"); 47 | } 48 | catch (HttpRequestException e) 49 | { 50 | await JS.InvokeAsync("alert", e.Message); 51 | ServerVerificatiing = false; 52 | StateHasChanged(); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/Pages/Step2.razor: -------------------------------------------------------------------------------- 1 | @page "/valid" 2 | @inject NavigationManager Navigation 3 | 4 |
5 | Your post is valid. 6 |
7 | 8 |
9 | 10 |
11 | 12 | @code { 13 | 14 | private void OnClickGoBack() 15 | { 16 | Navigation.NavigateTo("/"); 17 | } 18 | } -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace BlazorReCaptchaSample.Client 8 | { 9 | public class Program 10 | { 11 | public static async Task Main(string[] args) 12 | { 13 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 14 | builder.RootComponents.Add("app"); 15 | 16 | builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 17 | 18 | await builder.Build().RunAsync(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | #region 4 | namespace BlazorReCaptchaSample.Shared { } 5 | #endregion -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55005/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "BlazorReCaptchaSample.Client": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:55008/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/ReCAPTCHA.razor: -------------------------------------------------------------------------------- 1 | @using System.ComponentModel 2 | @inject IJSRuntime JS 3 | 4 |
5 | 6 | @code { 7 | 8 | [Parameter] 9 | public string SiteKey { get; set; } 10 | 11 | [Parameter] 12 | public EventCallback OnSuccess { get; set; } 13 | 14 | [Parameter] 15 | public EventCallback OnExpired { get; set; } 16 | 17 | private string UniqueId = Guid.NewGuid().ToString(); 18 | 19 | private int WidgetId; 20 | 21 | protected override async Task OnInitializedAsync() 22 | { 23 | await JS.InvokeAsync("My.reCAPTCHA.init"); 24 | WidgetId = await JS.InvokeAsync("My.reCAPTCHA.render", DotNetObjectReference.Create(this), UniqueId, SiteKey); 25 | } 26 | 27 | [JSInvokable, EditorBrowsable(EditorBrowsableState.Never)] 28 | public void CallbackOnSuccess(string response) 29 | { 30 | if (OnSuccess.HasDelegate) 31 | { 32 | OnSuccess.InvokeAsync(response); 33 | } 34 | } 35 | 36 | [JSInvokable, EditorBrowsable(EditorBrowsableState.Never)] 37 | public void CallbackOnExpired() 38 | { 39 | if (OnExpired.HasDelegate) 40 | { 41 | OnExpired.InvokeAsync(null); 42 | } 43 | } 44 | 45 | public ValueTask GetResponseAsync() 46 | { 47 | return JS.InvokeAsync("My.reCAPTCHA.getResponse", WidgetId); 48 | } 49 | } -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/SampleSite.Components.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 3.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.JSInterop 7 | @using BlazorReCaptchaSample.Client 8 | @using BlazorReCaptchaSample.Shared 9 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sample-by-jsakamoto/Blazor-UseGoogleReCAPTCHA/2cdabb630403350f6cc6e013e1c62e924e266be6/BlazorWasmEdition/Client/wwwroot/favicon.ico -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Blazor reCAPTCHA Sample Site 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
Loading...
18 | 19 |
20 | 21 |
22 | An unhandled error has occurred. 23 | Reload 24 | 🗙 25 |
26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/wwwroot/scripts/script.js: -------------------------------------------------------------------------------- 1 | var My; 2 | (function (My) { 3 | var reCAPTCHA; 4 | (function (reCAPTCHA) { 5 | let scriptLoaded = null; 6 | function waitScriptLoaded(resolve) { 7 | if (typeof (grecaptcha) !== 'undefined' && typeof (grecaptcha.render) !== 'undefined') 8 | resolve(); 9 | else 10 | setTimeout(() => waitScriptLoaded(resolve), 100); 11 | } 12 | function init() { 13 | const scripts = Array.from(document.getElementsByTagName('script')); 14 | if (!scripts.some(s => (s.src || '').startsWith('https://www.google.com/recaptcha/api.js'))) { 15 | const script = document.createElement('script'); 16 | script.src = 'https://www.google.com/recaptcha/api.js?render=explicit'; 17 | script.async = true; 18 | script.defer = true; 19 | document.head.appendChild(script); 20 | } 21 | if (scriptLoaded === null) 22 | scriptLoaded = new Promise(waitScriptLoaded); 23 | return scriptLoaded; 24 | } 25 | reCAPTCHA.init = init; 26 | function render(dotNetObj, selector, siteKey) { 27 | return grecaptcha.render(selector, { 28 | 'sitekey': siteKey, 29 | 'callback': (response) => { dotNetObj.invokeMethodAsync('CallbackOnSuccess', response); }, 30 | 'expired-callback': () => { dotNetObj.invokeMethodAsync('CallbackOnExpired'); } 31 | }); 32 | } 33 | reCAPTCHA.render = render; 34 | function getResponse(widgetId) { 35 | return grecaptcha.getResponse(widgetId); 36 | } 37 | reCAPTCHA.getResponse = getResponse; 38 | })(reCAPTCHA = My.reCAPTCHA || (My.reCAPTCHA = {})); 39 | })(My || (My = {})); 40 | //# sourceMappingURL=script.js.map -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/wwwroot/scripts/script.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"script.js","sourceRoot":"","sources":["script.ts"],"names":[],"mappings":"AAEA,IAAU,EAAE,CAmCX;AAnCD,WAAU,EAAE;IAAC,IAAA,SAAS,CAmCrB;IAnCY,WAAA,SAAS;QAClB,IAAI,YAAY,GAAyB,IAAI,CAAC;QAE9C,SAAS,gBAAgB,CAAC,OAAmB;YACzC,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,WAAW,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,WAAW;gBAAE,OAAO,EAAE,CAAC;;gBAC5F,UAAU,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC;QAC1D,CAAC;QAED,SAAgB,IAAI;YAEhB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC;YACpE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,UAAU,CAAC,yCAAyC,CAAC,CAAC,EAAE;gBACzF,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;gBAChD,MAAM,CAAC,GAAG,GAAG,yDAAyD,CAAC;gBACvE,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;gBACpB,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;gBACpB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;aACrC;YAED,IAAI,YAAY,KAAK,IAAI;gBAAE,YAAY,GAAG,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;YAExE,OAAO,YAAY,CAAC;QACxB,CAAC;QAde,cAAI,OAcnB,CAAA;QAED,SAAgB,MAAM,CAAC,SAAc,EAAE,QAAgB,EAAE,OAAe;YACpE,OAAO,UAAU,CAAC,MAAM,CAAC,QAAQ,EAAE;gBAC/B,SAAS,EAAE,OAAO;gBAClB,UAAU,EAAE,CAAC,QAAgB,EAAE,EAAE,GAAG,SAAS,CAAC,iBAAiB,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACjG,kBAAkB,EAAE,GAAG,EAAE,GAAG,SAAS,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;aAClF,CAAC,CAAC;QACP,CAAC;QANe,gBAAM,SAMrB,CAAA;QAED,SAAgB,WAAW,CAAC,QAAgB;YACxC,OAAO,UAAU,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC;QAFe,qBAAW,cAE1B,CAAA;IACL,CAAC,EAnCY,SAAS,GAAT,YAAS,KAAT,YAAS,QAmCrB;AAAD,CAAC,EAnCS,EAAE,KAAF,EAAE,QAmCX"} -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/wwwroot/scripts/script.ts: -------------------------------------------------------------------------------- 1 | declare var grecaptcha: any; 2 | 3 | namespace My.reCAPTCHA { 4 | let scriptLoaded: Promise | null = null; 5 | 6 | function waitScriptLoaded(resolve: () => void) { 7 | if (typeof (grecaptcha) !== 'undefined' && typeof (grecaptcha.render) !== 'undefined') resolve(); 8 | else setTimeout(() => waitScriptLoaded(resolve), 100); 9 | } 10 | 11 | export function init() { 12 | 13 | const scripts = Array.from(document.getElementsByTagName('script')); 14 | if (!scripts.some(s => (s.src || '').startsWith('https://www.google.com/recaptcha/api.js'))) { 15 | const script = document.createElement('script'); 16 | script.src = 'https://www.google.com/recaptcha/api.js?render=explicit'; 17 | script.async = true; 18 | script.defer = true; 19 | document.head.appendChild(script); 20 | } 21 | 22 | if (scriptLoaded === null) scriptLoaded = new Promise(waitScriptLoaded); 23 | 24 | return scriptLoaded; 25 | } 26 | 27 | export function render(dotNetObj: any, selector: string, siteKey: string): number { 28 | return grecaptcha.render(selector, { 29 | 'sitekey': siteKey, 30 | 'callback': (response: string) => { dotNetObj.invokeMethodAsync('CallbackOnSuccess', response); }, 31 | 'expired-callback': () => { dotNetObj.invokeMethodAsync('CallbackOnExpired'); } 32 | }); 33 | } 34 | 35 | export function getResponse(widgetId: number): string { 36 | return grecaptcha.getResponse(widgetId); 37 | } 38 | } -------------------------------------------------------------------------------- /BlazorWasmEdition/Client/wwwroot/style.css: -------------------------------------------------------------------------------- 1 | body, button { 2 | font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; 3 | font-size: 18px; 4 | } 5 | 6 | .section { 7 | margin: 16px; 8 | } 9 | 10 | button { 11 | color: #fff; 12 | border: none; 13 | padding: 10px 40px; 14 | background-color: darkblue; 15 | border-radius: 4px; 16 | height: 40px; 17 | transition: all linear 0.2s; 18 | } 19 | 20 | button:hover { 21 | background-color: rgb(100,40,230); 22 | } 23 | 24 | button[disabled] { 25 | background-color: silver; 26 | } 27 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Host/BlazorReCaptchaSample.Host.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 7.3 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Host/Controllers/SampleAPIController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using BlazorReCaptchaSample.Shared; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Options; 8 | 9 | namespace BlazorReCaptchaSample.Server.Controllers 10 | { 11 | [Route("/api/sample")] 12 | public class SampleAPIController : Controller 13 | { 14 | private IHttpClientFactory HttpClientFactory { get; } 15 | 16 | private IOptions reCAPTCHAVerificationOptions { get; } 17 | 18 | public SampleAPIController(IHttpClientFactory httpClientFactory, IOptions reCAPTCHAVerificationOptions) 19 | { 20 | this.HttpClientFactory = httpClientFactory; 21 | this.reCAPTCHAVerificationOptions = reCAPTCHAVerificationOptions; 22 | } 23 | 24 | public async Task Post([FromBody]SampleAPIArgs args) 25 | { 26 | var url = "https://www.google.com/recaptcha/api/siteverify"; 27 | var content = new FormUrlEncodedContent(new Dictionary 28 | { 29 | {"secret", this.reCAPTCHAVerificationOptions.Value.Secret}, 30 | {"response", args.reCAPTCHAResponse} 31 | }); 32 | 33 | var httpClient = this.HttpClientFactory.CreateClient(); 34 | var response = await httpClient.PostAsync(url, content); 35 | response.EnsureSuccessStatusCode(); 36 | 37 | var verificationResponse = await response.Content.ReadAsAsync(); 38 | if (verificationResponse.Success) return Ok(); 39 | 40 | return BadRequest(string.Join(", ", verificationResponse.ErrorCodes.Select(err => err.Replace('-', ' ')))); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Host/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace BlazorReCaptchaSample.Server 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | BuildWebHost(args).Run(); 12 | } 13 | 14 | public static IWebHost BuildWebHost(string[] args) => 15 | WebHost.CreateDefaultBuilder(args) 16 | .UseConfiguration(new ConfigurationBuilder() 17 | .AddCommandLine(args) 18 | .Build()) 19 | .UseStartup() 20 | .Build(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Host/Properties/PublishProfiles/blazor-recaptcha-sample - Web Deploy.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | MSDeploy 9 | /subscriptions/65cc645f-bd92-445b-b80b-e2867964ecc7/resourcegroups/demo-resources/providers/Microsoft.Web/sites/blazor-recaptcha-sample 10 | demo-resources 11 | AzureWebSite 12 | Debug 13 | x86 14 | https://blazor-recaptcha-sample.azurewebsites.net 15 | True 16 | False 17 | 6e1e976d-315b-461f-8a47-dc6c0d69fea8 18 | blazor-recaptcha-sample.scm.azurewebsites.net:443 19 | blazor-recaptcha-sample 20 | 21 | True 22 | WMSVC 23 | True 24 | $blazor-recaptcha-sample 25 | <_SavePWD>True 26 | <_DestinationType>AzureWebSite 27 | netcoreapp3.0 28 | win-x86 29 | true 30 | <_IsPortable>false 31 | False 32 | 33 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Host/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55003/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "BlazorReCaptchaSample.Server": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:55004/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /BlazorWasmEdition/Host/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace BlazorReCaptchaSample.Server 8 | { 9 | public class Startup 10 | { 11 | private IConfiguration Configuration { get; } 12 | 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | // This method gets called by the runtime. Use this method to add services to the container. 19 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.Configure(Configuration.GetSection("reCAPTCHA")); 23 | services.AddHttpClient(); 24 | services.AddMvc(); 25 | } 26 | 27 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 28 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 29 | { 30 | if (env.IsDevelopment()) 31 | { 32 | app.UseDeveloperExceptionPage(); 33 | app.UseWebAssemblyDebugging(); 34 | } 35 | 36 | app.UseBlazorFrameworkFiles(); 37 | app.UseStaticFiles(); 38 | 39 | app.UseRouting(); 40 | 41 | app.UseEndpoints(endpoints => 42 | { 43 | endpoints.MapDefaultControllerRoute(); 44 | endpoints.MapFallbackToFile("index.html"); 45 | }); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Host/appSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "reCAPTCHA": { 3 | "Secret": "" 4 | } 5 | } -------------------------------------------------------------------------------- /BlazorWasmEdition/Host/reCAPTCHAVerificationOptions.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorReCaptchaSample.Server 2 | { 3 | public class reCAPTCHAVerificationOptions 4 | { 5 | public string Secret { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Host/reCAPTCHAVerificationResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace BlazorReCaptchaSample.Server 5 | { 6 | public class reCAPTCHAVerificationResponse 7 | { 8 | public bool Success { get; set; } 9 | 10 | // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ) 11 | [JsonProperty("challenge_ts")] 12 | public DateTimeOffset ChallengeTimestamp { get; set; } 13 | 14 | // the hostname of the site where the reCAPTCHA was solved 15 | public string Hostname { get; set; } 16 | 17 | [JsonProperty("error-codes")] 18 | public string[] ErrorCodes { get; set; } = new string[0]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Shared/BlazorReCaptchaSample.Shared.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 7.3 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BlazorWasmEdition/Shared/SampleAPIArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace BlazorReCaptchaSample.Shared 8 | { 9 | public class SampleAPIArgs 10 | { 11 | public string reCAPTCHAResponse { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blazor Samples - use Google reCAPTCHA v2 2 | 3 | ## Live Demo 4 | 5 | - https://blazor-recaptcha-sample.azurewebsites.net/ 6 | 7 | ## Source Code 8 | 9 | ### Blazor WebAssembly App (Client-Side Blazor) Edition 10 | 11 | - [tree/master/BlazorWasmEdition](https://github.com/sample-by-jsakamoto/Blazor-UseGoogleReCAPTCHA/tree/master/BlazorWasmEdition) 12 | 13 | ### Blazor Server App (Server-Side Blazor) Edition 14 | 15 | - [tree/master/BlazorServerEdition/Server](https://github.com/sample-by-jsakamoto/Blazor-UseGoogleReCAPTCHA/tree/master/BlazorServerEdition/Server) 16 | 17 | ## Requirements 18 | 19 | - [.NET Core 3.1 SDK (3.1.300, or later)](https://dotnet.microsoft.com/download/dotnet-core/3.1) 20 | 21 | ## License 22 | 23 | [The Unlicense](LICENSE) --------------------------------------------------------------------------------