Xamarin で PCL だけ使って指紋認証したい

# はじめに
こんにちは。@albno273 です。
研究で Xamarin (Xamarin.Forms) を用いたアプリを開発することになり、あまり経験のない C# と格闘の毎日を送っています。

そのアプリに指紋認証を用いたログイン機能を実装したくなったのですが、「Xamarin Forms 指紋」で Google 検索してもそれらしき日本語の記事が一切ヒットせず、一番上に出てくる [Stack Overflow の記事](http://stackoverflow.com/questions/35706624/xamarin-forms-with-touch-id-fingerprint-authentication) には「Dependency Service を使え」とあるのみで、どうにか PCL で完結するように書けないだろうか……と思っていました。
とは言っても指紋認証機能は OS の機能に強く依存するところなので、半ば諦め気味で探っていると、ありました。

[Fingerprint plugin for Xamarin](https://www.nuget.org/packages/Plugin.Fingerprint/)

これを使ったところ、ほんの少し PCL に記述するだけで実装できたので感動のあまり、そして日本語記事がないので参考になればと思い、この記事を書くことにしました。

**注) ただし指紋認証機能がある Android 端末が手元になく、動作確認できていないので、「少なくとも iOS 端末はカバーできる」くらいで読んでもらえると嬉しいです。ごめんなさい……**

# サンプル
ほぼ本家の写経になってしまっていますが、一部修正したいところがあったのでサンプルを書きました。

[albno273/XamarinFormsFingerprintSample - Xamarin.Forms で PCL だけで指紋認証するサンプル](https://github.com/albno273/XamarinFormsFingerprintSample)

# つかいかた
[このあたりからの30行弱](https://github.com/albno273/XamarinFormsFingerprintSample/blob/master/FingerprintSample/FingerprintSamplePage.xaml.cs#L17)がすべてです。

```csharp:FingerprintSamplePage.xaml.cs(抜粋)
private CancellationTokenSource _cancel;

private async void OnAuthenticate(object sender, EventArgs e)
{
    _cancel = swAutoCancel.IsToggled ? new CancellationTokenSource(TimeSpan.FromSeconds(10)) : new CancellationTokenSource();
    lblStatus.Text = "";

    if (await Plugin.Fingerprint.CrossFingerprint.Current.IsAvailableAsync())
    {
        var result = await Plugin.Fingerprint.CrossFingerprint.Current.AuthenticateAsync("指を置いてね！", _cancel.Token);
        await SetResultAsync(result);
    }
    else
    {
        await DisplayAlert("注意", "指紋認証機能は使用できません！", "OK");
    }
}

private async Task SetResultAsync(FingerprintAuthenticationResult result)
{
    if (result.Authenticated)
    {
        await Navigation.PushAsync(new FingerprintAuthenticatedPage());
    }
    else
    {
        lblStatus.Text = $"{result.Status}: {result.ErrorMessage}";
    }
}
```

## 指紋認証をするには
``AuthenticateAsync("指を置いてね！", _cancel.Token)``の部分。
第一引数に指定した文字列がダイアログに表示されます。
ダイアログを自動で閉じたい時には第二引数に Token を指定します。
第二引数を指定しない場合はキャンセルボタンを押さない限りダイアログは閉じません。
サンプルでは``CancellationTokenSource(TimeSpan.FromSeconds(10))``として、10秒反応がなければダイアログが自動で閉じるようになっています。

## 認証が成功したのか失敗したのか知りたい
``FingerprintAuthenticationResult.Authenticated`` の true / false で判別。
認証が成功した時に true が、
キャンセルボタンを押した時、認証を失敗した後に「パスワードを入力」ボタンを押した時、規定回数認証に失敗した時に false が返ってきます。

## そもそもこの端末で指紋認証機能が使えるのか知りたい
``IsAvailableAsync()`` の true / false で判別。
このチェックが本家のサンプルになかったのが自分で写経した理由です。
おそらく指紋認証機能がない端末で``AuthenticateAsync()``を呼ぶと例外を吐かれるので、これの結果で処理を分けたほうが丁寧なんじゃないかと思いました。

# 実際の画面
ボタンを押したところ。
認証に成功すると次の画面に遷移します(なにもないので割愛)。
![指紋認証ダイアログ出現時](https://qiita-image-store.s3.amazonaws.com/0/163084/808ca383-6d8e-7d47-4ddf-f18c01d540ea.png)

認証失敗時。
![認証失敗時](https://qiita-image-store.s3.amazonaws.com/0/163084/03685544-d137-c91f-58c5-326bf6eb810d.png)

キャンセルボタンを押したところ。
``FingerprintAuthenticationResult.Status``は``Canceled``になります。
![キャンセルボタン押下時](https://qiita-image-store.s3.amazonaws.com/0/163084/998bff8c-31f3-9f10-58af-e5e461190976.png)

「パスワードを入力」ボタンを押したところ。
``FingerprintAuthenticationResult.Status``は``FallbackRequested``になります。
この値を受けた時にパスワード入力ダイアログを出せば完璧ですね。
![スクリーンショット 2017-05-18 01.11.51.png](https://qiita-image-store.s3.amazonaws.com/0/163084/6f1d3dbf-986b-cddf-790d-9d9bae19b14b.png)

Token によってキャンセルされたところ。
``FingerprintAuthenticationResult.Status``は``Canceled``ですが、``ErrorMessage``が変わるので手動キャンセルの場合と挙動を変えることが出来ます。
![自動キャンセル時](https://qiita-image-store.s3.amazonaws.com/0/163084/0e0500a9-7f7f-ed44-5c26-cc7716e1a973.png)

指紋認証機能が使えない場合。
![指紋認証使用不可時](https://qiita-image-store.s3.amazonaws.com/0/163084/f8625067-a3d5-7460-c289-a8558e23470d.png)

Android でも同じように動きます。
![指紋認証使用不可時 / Android](https://qiita-image-store.s3.amazonaws.com/0/163084/b9967dd6-4b11-6f17-c6e5-d262b3afc068.png)

# まとめ
+ TouchID はこのプラグインを使えば PCL への記述だけで使用可能
+ Nexus Imprint や SAMSUNG Galaxy でも動くはず(要検証)

# おわりに
自分で作ったプラグインでもないのに長々と説明してしまっていいのだろうかという気もしますが、誰かの参考になれば幸いです。
Xamarin (というか .NET) 初心者だったので NuGet は偉大だなぁとしみじみ感じました。