Xamarin.FormsでのAzure SignalRのセキュリティにおける注意点

以下の問題が発生していましたが解決したので記録しておきます。
同じような問題を抱える開発者もいると思うので参考になれば幸いです。

# 問題

SignalRの接続の際に以下のエラーが発生する場合がある。

>The remote server returned an error: (429) Too Many Requests.

このエラーは不定期で発生し、エラーが発生しない場合もある。

Azure Functionsのログストリームで確認すると明らかに不自然な挙動がある。

```ruby:AzureLogStream
Executing 'Negotiate' (Reason='This function was programmatically called via the host APIs.', Id=ﾕﾆｰｸid1)
Executed 'Negotiate' (Succeeded, Id=ﾕﾆｰｸid1, Duration=5ms)
Executing 'Negotiate' (Reason='This function was programmatically called via the host APIs.', Id=ﾕﾆｰｸid2)
Executed 'Negotiate' (Succeeded, Id=ﾕﾆｰｸid2, Duration=5ms)
Executing 'Negotiate' (Reason='This function was programmatically called via the host APIs.', ﾕﾆｰｸid3)
Executed 'Negotiate' (Succeeded, Id=ﾕﾆｰｸid3, Duration=5ms)
```
上記のエラーが出る場合はこのようなアクセスが集中している状態です。

典型的なDoS攻撃に見えます。

# 前提

Xamarin.FormsでのSignalRの実装は以下のサンプルを利用しています。
https://docs.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/webservices-azuresignalr/

サンプルは以下の構成になっています。

ChatServerではMicrosoft.Azure.WebJobs.Extensions.SignalRS 1.1.0を使用。
NegotiateのAuthorizationLevelがAnonymousになっている。

```C#:Negotiate.cs
    public static class Negotiate
    {
        [FunctionName("Negotiate")]
        public static SignalRConnectionInfo GetSignalRInfo(
            [HttpTrigger(AuthorizationLevel.Anonymous,"get",Route = "negotiate")]
            HttpRequest req,
            [SignalRConnectionInfo(HubName = "simplechat")]
            SignalRConnectionInfo connectionInfo)
        {
            return connectionInfo;
        }
    }
```

ChatClient側Negotiateの呼び出し

```C#:SignalRService.cs
        public async Task ConnectAsync()
        {
                //（略）
                string negotiateJson = await client.GetStringAsync($"{Constants.HostName}/api/negotiate");
                NegotiateInfo negotiate = JsonConvert.DeserializeObject<NegotiateInfo>(negotiateJson);

                HubConnection connection = new HubConnectionBuilder()
                    .AddNewtonsoftJsonProtocol()
                    .WithUrl(negotiate.Url, options =>
                    {
                        options.AccessTokenProvider = async () => negotiate.AccessToken;
                    })
                    .Build();
                //（略）
        }
```

# 解決策

Azure Functionsのセキュリティには関数キー（アプリキーとは別なので注意）というものがあります。
https://docs.microsoft.com/ja-jp/azure/azure-functions/security-concepts
※**関数のアクセス キー**あたりを参照

それを踏まえて実装

ChatServer側はAuthorizationLevel.AnonymousをFunctionに変更するだけ（Talkも修正してください）

```C#:Negotiate.cs
    public static class Negotiate
    {
        [FunctionName("Negotiate")]
        public static SignalRConnectionInfo GetSignalRInfo(
            [HttpTrigger(AuthorizationLevel.Function,"get",Route = "negotiate")]
            HttpRequest req,
            [SignalRConnectionInfo(HubName = "simplechat")]
            SignalRConnectionInfo connectionInfo)
        {
            return connectionInfo;
        }
    }
```

ChatClient側はAzure Function Keyを設定します（埋め込みはよくないので検討してください）

```C#:SignalRService.cs
        public async Task ConnectAsync()
        {
                //（略）
                string negotiateJson = await client.GetStringAsync($"{Constants.HostName}/api/negotiate?code={AZURE_FUNCTION_NEGOTIATE_KEY}");
                NegotiateInfo negotiate = JsonConvert.DeserializeObject<NegotiateInfo>(negotiateJson);

                HubConnection connection = new HubConnectionBuilder()
                    .AddNewtonsoftJsonProtocol()
                    .WithUrl(negotiate.Url, options =>
                    {
                        options.AccessTokenProvider = async () => negotiate.AccessToken;
                    })
                    .Build();
                //（略）
        }
```

# 結論

もともとサンプルを基に実装している段階でセキュアキーの設定がないことに？？？となっていたのですが、
参考にできるページがみつからなくて四苦八苦していました。
とはいえわかってしまえば当たり前な実装手法です。

今回は内部テストの段階で判明したので大事には至りませんでしたが、そのまま稼働させてたらと思うとぞっとします。

本当はサンプル内にコメントがほしいですがGitHubにissueできる英語力はないので他の人にお任せしますｗ