Sign in with Apple+Cordovaについて

Sign in with AppleをCordovaアプリで実装してみた際の記録です。

## Sign In with Appleとは
- Apple IDでアカウント作成・ログインができる 
- Touch ID/Face IDで認証できる 
- 登録するメールアドレスをランダムにできる

## 実装手段は２つ
- Sign In with Apple JS 
- AuthenticationServices (ネイティブ SDK)

## とりあえずJS版をCordovaに埋め込むと・・
CordovaはWebView上で動作するので、Sign In with Apple JSで事足りるのでは？と思い、JS版をCordovaに埋め込んでみました。
とりあえず、UIWebVeiw上で動作させてみたところ、SignInボタンをタップすると、ブラウザに飛びました。 
※ちなみに、生のUIWebViewだと無反応なので、そもそもUIWebViewは対応していないようです。

WkWebViewの場合は認証のモーダルが開きました。
ただ、CordovaのWebView内だとredirect先にページ遷移してしまうので、SPAで実装するCordovaアプリだとUX的にはネイティブSDK（AuthenticationServices）の方が良さそうです。
※redirect先がディープリンクならページ遷移せずにハンドリングできるかもしれませんが。

## AuthenticationServices
AuthenticationServicesはiOS12から追加された認証サービスのフレームワークです。Sign in with Apple関連のAPIも含まれています。

AuthenticationServicesを使用してログインのリクエストすると、認証モーダルが表示されます（Face ID/Touch ID/パスワードの何れかで認証） 
取得できる情報はID Token、認可コード、メールアドレス、ユーザーID、ユーザー名などです。

## Cordovaプラグイン化
CordovaアプリにおいてJSからネイティブのSDKにアクセスするには、Cordovaプラグインを作成する必要があります。
そこで、作ってみました。
https://github.com/pscsrv/cordova-plugin-apple-login

以下サンプルコードです。
一通りのパラメーターは渡せるようにしています。（各パラメーターの詳細は、Githubに書いてあるAppleのドキュメントを参照してください）

```js
SignInWithApple.request({
 requestedScopes: [ SignInWithApple.Scope.Email ],
 state: '...',
 nonce: '...',
}).then((credential) => {
 // ...
}).catch(error => {
 console.error(error)
})
```

認証に成功すると、以下のようなオブジェクトが返ってきます。ほぼネイティブSDKのママです。

```json
{
 "authorizedScopes": [],
 "identityToken": "eyJr.....K9NQ", // ID Token
 "authorizationCode": "c5704....eV6sg", // 認可コード
 "realUserStatus": 1,
 "fullName": { // ユーザー名(最初の1回のみ取得可)
   "namePrefix": null,
   "givenName": "Daisuke",
   "nameSuffix": null,
   "middleName": null,
   "familyName": "Kishino",
   "nickname": null
 },
 "email": "XXX@privaterelay.appleid.com", // メールアドレス(最初の1回のみ取得可)
 "state": null,
 "user": "XXX" // ユーザーID
}
```

## サーバー側
サーバー側はCordova特有のことはないですが、一応解説します。以下のような流れになります。

- 認可コードサーバー側に送る
    - ID Tokenでも良いが、リプライ攻撃やID Tokenの検証を考えると、認可コードの方が安全 
- Appleのtokenエンドポイントに投げて、ID Tokenを取得
- ID Tokenの署名や内容を検証して、OKなら会員登録/ログイン

以下フローです。オレンジ枠がAuthenticationServicesの担当箇所です。
<img width="676" alt="スクリーンショット 2019-12-07 23.33.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51815/12708cea-0958-672e-676a-90ecac637024.png">

ID TokenのPayloadは以下のようになっています。

```json
{
 "iss": "https://appleid.apple.com", // 発行者(Apple)
 "aud": "jp.co.pscsrv.baassampleapp", // 発行先(App)
 "exp": 1569548110, // 有効期限(10分)
 "iat": 1569547510, // 発行時間
 "sub": "XXX", // ユーザーID
 "at_hash": "dP9t…OBDPA", // 認可コードのHash
 "email": "XXX@privaterelay.appleid.com", // メールアドレス
 "email_verified": "true", // ↑が検証済みか？
 "is_private_email": "true", // ↑がPrivateか？
 "auth_time": 1569547508, // ユーザー認証の時刻
 "nonce": "XXX" // リプライ攻撃対策用のランダム値
}
```

## 注意点
- メールアドレスは初回だけ取得可能です（ユーザーがRevokeすると、再取得可能） 
- ユーザー名はtokenエンドポイントから返ってこない（AuthenticationServices経由でのみ取得可能） 
- client_secretはJWT（有効期限最大６ヶ月） 
- PrivateメールアドレスはTeam ID単位に作成される 
- ユーザーIDはRevokeされても変わらない。（メアドも変わらな
い。過去の記事など見ると変わるという記述もあるが・・）

## サンプル
Cordovaプラグインの使用サンプルと、サーバー側実装のサンプルです。

Cordovaプラグインの使用サンプル
https://github.com/kishino/cordova-plugin-apple-login-sample

サーバー側実装
https://gist.github.com/kishino/281f13461e975c819161298d35b6de54

## さいごに
実際にサンプルを使った感じ、iPhoneでのログインのUXはとても良いです。
CordovaでもSign in with Appleを実装して、より良いログイン体験を目指したいです！