初心者おばちゃんの簡単ASP.NET Razor Pages / ReCAPTCHAセッティング

#reCAPTCHAとは
>ウェブサイトの制限エリアへのアクセスを試みるボットからサイトを防御するためCAPTCHAを利用するのと同時に、そのCAPTCHAに対する返答を紙の本のデジタル化に活かすシステムである。（ウィキペディア参照）

とあります。
意外と情報が少なく、初心者にはハードルが高かったので、この度わかる範囲でシェアさせていただきました。つたない説明やコーディングで申し訳ございませんが、お付き合いください。

#設定方法
*横スクロールの関係で見やすくするためにコードを切ってしまってますが、はぶいているラインはありません。

###１．使用言語と準備
- ASP.NET Core
- Razor Pages
- Google reCAPTCHA

###２．Google reCAPTCHAの設定
こちらからadmin console(設定)へ
https://www.google.com/recaptcha/intro/v3.html

- Label ターゲットサイトの名前をReCAPCTA用につけてやります。
- reCAPTCHA type　シークレット・キーが生成されています。
 
 * SiteKey : "xxxxxxxxxxxxxxxxxxxxxxVG999999999999999999"
 * SecretKey : "9LQQQQQQQQQQQQQQQQQQzzzzzzzzzzzzzzzzzzzz"
 
- Domains
 * 本番用 - yourdomain.com
 * ローカル用 - localhost
- Owners　ご自身のメールアドレス
- Security Preference　必要に合わせて
- Verify the origin of reCAPTHA solutions　reCAPTCHA
ソリューションの起源を確認する
無効にした場合、ソリューションを検証するときにサーバーのホスト名を確認する必要があります。
- Send alerts to owners　Send alerts to owners
Receive alerts if Google detects problems with your site, such as a misconfiguration or an increase in suspicious traffic.
所有者にアラートを送信所有者にアラートを送信
Googleが設定の誤りや疑わしいトラフィックの増加など、サイトの問題を検出した場合にアラートを受信します。

###３．キーの保存先
appsettings.json

```
{
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"
        }
    },
    "reCAPTCHA": {
        "SiteKey": "xxxxxxxxxxxxxxxxxxxxxxVG999999999999999999",
        "SecretKey": "9LQQQQQQQQQQQQQQQQQQzzzzzzzzzzzzzzzzzzzz"
    }
   }
}
```

###４．フロントエンドへの設定（ターゲットページ）　
Index.cshtml

```
@page

@using Microsoft.Extensions.Configuration
@model xxxx.zzzzz.testIndex
@inject IConfiguration configuration
```
```
<div class="container">
    <main role="main" class="pb-3">
        <h1>Log In</h1>
        <div class="row">
            <div class="span12 p-3" style="min-width:100%">
                <div class="row">
                    <div class="font-weight-bold span6 col-sm-6 p-3">
                        <div>
                            <p class="text-danger">@Model.Message</p>
                        </div>
                        <form method="post">
                            <div class="form-group">
                                <label asp-for="UserName" class="control-label">User Name</label>
                                <div class="col-md-10">
                                    <input asp-for="UserName" />
                                </div>
                            </div>
```
```
                            <div class="form-group">
                                <label asp-for="Password" class="control-label">Password</label>
                                <div class="col-md-10">
                                    <input asp-for="Password" />
                                </div>
                            </div>
                            <div class="form-group">
                                <label asp-for="ID" class="control-label">ID</label>
                                <div class="col-md-10">
                                    <input asp-for="ID" />
                                </div>
                            </div>
```
```
                            <div class="g-recaptcha" data-sitekey="@configuration["reCAPTCHA:SiteKey"]"></div>
                            <br />
                            <div class="form-group">
                                <button class="btn btn-primary">Log in</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
```
```
        @section Scripts {
            @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
            <script src="https://www.google.com/recaptcha/api.js" async defer></script>
        }
    </main>
```

###５．バックエンドへの設定１（モデル）　
reCAPTCHA.cs

```
namespace XXX.Models
{
    public class reCAPTCHA
    {
        public string SiteKey { get; set; }
        public string SecretKey { get; set; }
    }
}
```

###６．バックエンドへの設定２（ターゲットページ）
Index.cshtml.cs

```
using System.ComponentModel.DataAnnotations;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Web;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
```
```
namespace xxx.zzzz.vvvv
{
    public class testIndexModel : PageModel
    {
        private readonly IConfiguration configuration;

        [BindProperty]
        public string UserName { get; set; }
        [BindProperty]
        public int ID { get; set; }

        [BindProperty, DataType(DataType.Password)]
        public string Password { get; set; }

        public string Message { get; set; }

        public IHttpClientFactory ClientFactory { get; }

        public ILogger Logger { get; }
        public object HttpHelper { get; private set; }
```
```
        public testIndexModel(ILogger<testIndexModel> logger, 
            IConfiguration configuration, 
            IHttpClientFactory clientFactory)
        {
            Logger = logger;
            this.configuration = configuration;
            ClientFactory = clientFactory;
        }

        public void OnGet(string message)
        {
            Message = message;
        }
```
```
        public async Task<IActionResult> OnPostAsync(xxx.Models.testuser testuser)//** ユーザー認証のモデル **//
        {
            string recaptchaResponse = this.Request.Form["g-recaptcha-response"];
            var client = ClientFactory.CreateClient();
            var reCAPTCHA = configuration.GetSection("reCAPTCHA").Get<xxx.Models.reCAPTCHA>();

            try
            {
                var parameters = new Dictionary<string, string>
                {
                    {"secret", reCAPTCHA.SecretKey},
                    {"response", recaptchaResponse},
                    {"remoteip", this.HttpContext.Connection.RemoteIpAddress.ToString()}
                };
```
```
                HttpResponseMessage response = await client.PostAsync("https://www.google.com/recaptcha/api/siteverify", new FormUrlEncodedContent(parameters));
                response.EnsureSuccessStatusCode();

                string apiResponse = await response.Content.ReadAsStringAsync();
                dynamic apiJson = JObject.Parse(apiResponse);
                if (apiJson.success != true)
                {
                    this.ModelState.AddModelError(string.Empty, "ログインエラーが発生しました ");
                }
```
```
                if (ModelState.IsValid)
				//**　ユーザー認証を行います　**//
                {
                    if (testuser.IsValid(testuser.UserName, testuser.Password, ((int)testuser.ID).ToString()))
                    {
                        Response.Redirect("/xxx/zzzz/testIndex?id=" + HttpUtility.UrlEncode(((int)testuser.ID).ToString()));
                    }
                }
                Message = "ログインエラーが発生しました";
            }
            catch (HttpRequestException ex)
            {
                this.Logger.LogError(ex, "Unexpected error calling reCAPTCHA api.");
            }
            return Page();
        }
    }
}

```

## 動画集
[初心者おばちゃんの簡単ASP.NET Razor PagesでRESTFul／Ajax使ってみよう](https://www.youtube.com/watch?v=dfJb2Wb6h3E)
[実際に使ってるOboeroシステムの動画]
(https://studio.youtube.com/video/RLMCMSeNsxE/edit/basic)

## Oboero メモリーシステム
[Oboero メモリーシステム](https://oboero3.azurewebsites.net)