iOSのアプリをアップデートしたときにリジェクトをくらった理由と対応

# はじめに
iOSのバージョンアップもあり、[CotoGoto](https://cotogoto.ai/)のアプリの最新化を図るために久しぶりにライブラリのアップデートをしたのですが、リジェクトになってしまい、その理由と対応をまとめておきます。

まずは、リジェクトをくらった内容ですね。

> Guideline 4.0 - Design
> 
> We noticed that the user is taken to the default web browser to sign in or register for an account, which provides a poor user experience.
> 
> Next Steps
> 
> To resolve this issue, please revise your app to enable users to sign in or register for an account in the app.
> 
> You may also choose to implement the Safari View Controller API to display web content within your app. The Safari View Controller allows the display of a URL and inspection of the certificate from an embedded browser in an app so that customers can verify the webpage URL and SSL certificate to confirm they are entering their sign in credentials into a legitimate page.
> 
> Resources
> 
> For additional information on the Safari View Controller API, please review the [What's New in Safari](https://developer.apple.com/library/archive/releasenotes/General/WhatsNewInSafari/Introduction/Introduction.html) webpage.
> 
> Guideline 4.8 - Design - Sign in with Apple
> 
> Your app uses a third-party login service, but does not offer Sign in with Apple. Apps that use a third-party login service for account authentication need to offer Sign in with Apple to users as an equivalent option.
> 
> Next Steps
> 
> Please revise your app to offer Sign in with Apple as an equivalent option for account authentication.
> 
> Resources
> 
> - Review [Sign in with Apple sample code](https://developer.apple.com/documentation/authenticationservices/implementing_user_authentication_with_sign_in_with_apple).
> - For an overview of design and formatting recommendations for Sign in with Apple, see the [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/technologies/sign-in-with-apple/introduction) .
> - Learn about the [benefits of Sign in with Apple](https://developer.apple.com/sign-in-with-apple/).

主には２点の指摘で、

1. ユーザー登録の画面遷移の流れをWebサイト（[CotoGoto](https://cotogoto.ai/)）で登録するように促していたのですが、その際にブラウザ起動していた部分が、別アプリでブラウザが起動するので、UI的にダメですよ。
2. ユーザー登録する際に、サードパーティのログイン連携をしているアプリはSign In with Appleの実装が必須ですよ。

が指摘されました。
以前のアプリ申請には通っていたので、最近の流れでダメになったのですね。
なので、この２点を対応していきます。

# 環境
### アプリ側
* Monaca
* Cordova 11.0.0
### サーバ側
* Tomcat 9.0
* Java 11

# 1. アプリ内で Web コンテンツを表示する対応
このアプリではMonacaを使って開発をしています。

Appleからの改修の提案では、Safari View Controller APIを使うとアプリに埋め込まれたブラウザーから URLを表示し、証明書を検査できるため、ユーザーはWebページのURLとSSL証明書を検証して、ユーザー登録を正当なページに入力していることを説明していますね。

ただ、CordovaでSafari View Controller APIを利用するためには、カスタムプラグインが必要で、そちらを利用して対応したいと思います。

https://github.com/EddyVerbruggen/cordova-plugin-safariviewcontroller#6-reading-safari-data-and-cookies-with-cordova

こちらは、プラグインをインポートして、以下のような感じで呼び出しの関数を作れば、対応できました。

```javascript
function openUrl(url, readerMode) {
  SafariViewController.isAvailable(function (available) {
    if (available) {
      SafariViewController.show({
            url: url,
            hidden: false, // default false. You can use this to load cookies etc in the background (see issue #1 for details).
            animated: false, // default true, note that 'hide' will reuse this preference (the 'Done' button will always animate though)
            transition: 'curl', // (this only works in iOS 9.1/9.2 and lower) unless animated is false you can choose from: curl, flip, fade, slide (default)
            enterReaderModeIfAvailable: readerMode, // default false
            tintColor: "#00ffff", // default is ios blue
            barColor: "#0000ff", // on iOS 10+ you can change the background color as well
            controlTintColor: "#ffffff" // on iOS 10+ you can override the default tintColor
          },
          // this success handler will be invoked for the lifecycle events 'opened', 'loaded' and 'closed'
          function(result) {
            if (result.event === 'opened') {
              console.log('opened');
            } else if (result.event === 'loaded') {
              console.log('loaded');
            } else if (result.event === 'closed') {
              console.log('closed');
            }
          },
          function(msg) {
            console.log("KO: " + msg);
          })
    } else {
      // potentially powered by InAppBrowser because that (currently) clobbers window.open
      window.open(url, '_blank', 'location=yes');
    }
  })
}
```

これで、アプリ内でブラウザが開けるようになりました。
InAppBrowserとのプラグインの差がわかりにくいですが、アプリ外のブラウザに移動しないので？、UI的にもよくなったと思います。 

# 2. Sign in with Appleの対応
2020年4月が期限で、[Sign in with Apple](https://developer.apple.com/design/human-interface-guidelines/technologies/sign-in-with-apple/introduction)の対応をしないといけないのですが、前回のアップデートは緊急リリース対応したので、そういえば、何か言われていた記憶もあります。
今回は、ちゃんと対応しないといけないので、Sign in with Appleの対応をしていきます。

今回はアプリはある状態なので、ボタン追加の部分の対応の部分だけを記述していきます。

## 2-1. AppleDeveloperの設定
Apple の開発者サイトの [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list)ページで、アプリの Sign In with Apple を有効にします。

対象のアプリを選択して、その後、Capabilitiesの一覧から「Sign In with Apple」のチェックボックスにチェックを入れて保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/5403d3df-b90e-5d7a-05ee-533c8609bd98.png)

Editボタンを押して「Enable as a primary App ID（プライマリ アプリ ID として有効にする）」を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/5d370798-1c65-46e5-2cc9-ba8714df7d0e.png)
Identifiersに移動します。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f32303436342f35343935373538612d633233302d373637362d353433392d6439623635663261306461662e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/bc2319f7-a937-29d7-ff54-562353145646.png)
新規で「Services Id」タイプの識別子を作成します。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f32303436342f32656663313563632d343565612d303966352d613530342d6164316135306634343730362e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/f3ddd2fc-f0ea-7f90-ae81-c88d2f60be90.png)
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f32303436342f32646430636265632d386338662d303733312d653664652d6565623130353666346439392e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/556efd8b-07cb-efaa-0c7c-5ca11b358b41.png)
作成したら、Identifiersのページに戻り、作成した「Services Id」を選択します。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f32303436342f65353335636264372d373264312d643139342d323033322d3861356231636636663466312e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/64f2582b-0238-cf32-410c-09c9355d7966.png)
そうすると、Sign In with Appleが表示されているので、チェックを入れて、Configureボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/681f443c-97d1-5377-0af1-639912a64dbc.png)
ここで、連携させたいApp IDを選択して、WebサイトのドメインやコールバックURLを登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/cf761aa5-d55e-2979-8f58-e47f9b79eb6e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/2b0507a4-3e08-7518-7fdc-39da9b88b3cf.png)
そして、登録が完了します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/75131781-2a78-626b-e720-d2c92f961309.png)

## 2-2. Keyの作成
Appleの秘密鍵を作成し、ダウンロードします。
メニューからKeysに移動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/73ea9c67-d2a7-b892-35b3-54b57b76315a.png)
Sign In with Appleをオンにし、Configureボタンをクリックします。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/d651a413-bae0-6974-7d20-b05d9ad061f0.png)
画面で、作成した「Primary App Id」を選択します。Save、Continueの順にクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/19356d76-46cc-9e27-b5e0-8e522d0071f9.png)
Registerをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/fde81e26-db85-e8db-2227-48d654dc7e4d.png)
これで秘密鍵を 1 回限りでダウンロードできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20464/91d679ea-3175-9ac4-7d0e-6a863979e2b8.png)

## 2-3. Authorizeエンドポイント
Appleの認証ページURLのクエリパラメータ、レスポンスについては「[Incorporating Sign in with Apple into Other Platforms](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms)」に記載があります。

このURLに対してHTTP GETでrequestを投げて、Appleのログイン画面に遷移します。
```
https://appleid.apple.com/auth/authorize
```

主には、`response_type`、`client_id`、`redirect_uri`の項目の指定が必要になります。
`redirect_uri`は、AppleDeveloperの設定で事前に登録しておく必要があります。

## 2-4. Tokenエンドポイント
Appleのトークン発行エンドポイントにリクエストを行い、トークンの発行を行います。
パラメータ、レスポンスについては「[Generate and validate tokens](https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens)」に記載があります。
パラメータは**Authorization**のものを使います。

```
https://appleid.apple.com/auth/token
```

### `client_secret`の値のJSON Web Token(JWT)作成
Apple Developers で発行したKeyを用いて、リクエストパラメータ`client_secret`に付与するJWTの作成を行います。
JWTとは、`header`と`payload`から成るJSON構造をエンコードし電子署名をつけたものです。
デコード&エンコードしたJWTの検証には、 https://jwt.io/ を利用します。

JavaでのJWTのエンコード&署名の例は以下です。

```xml:pom.xml
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.70</version>
</dependency>
```
`client_secret`の生成部分の実装
```java
private static String generateJWT() throws Exception {

    final var privateKey = getPrivateKey();
    final var expiration = 1000 * 60 * 5;

    // JWTを生成
    final var token = Jwts.builder()
            .setHeaderParam(JwsHeader.ALGORITHM, SignatureAlgorithm.ES256.getValue())
            .setHeaderParam(JwsHeader.KEY_ID, "Apple Developerで発行したKey ID")
            .setIssuer("Apple DeveloperでのTeam ID")
            .setAudience("https://appleid.apple.com")
            .setSubject("Apple Developerで発行したService ID") 
            .setExpiration(new Date(System.currentTimeMillis() + expiration))
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .signWith(privateKey, SignatureAlgorithm.ES256) // ECDSA using P-256 and SHA-256
            .compact();

    return token;
}

private static PrivateKey getPrivateKey() throws IOException {

    final Reader pemReader = new InputStreamReader(
            AuthToken.class.getResourceAsStream("作成した秘密鍵のファイルパス"));
    final var pemParser = new PEMParser(pemReader);
    final var converter = new JcaPEMKeyConverter();
    final var object = (PrivateKeyInfo) pemParser.readObject();
    final var pKey = converter.getPrivateKey(object);

    return pKey;
}
```

### IDなどのユーザー情報
Tokenエンドポイントのresponseの中に`id_token`が含まれています。
`id_token`を以下のような形で、デコードしていくと`sub`からユーザーIDを取得することができます。

```java
final var idToken = (String) map.get("id_token");
final var array = idToken.split("\\.");
final var decode = Base64.getDecoder().decode(array[1]);
final var map2 = (Map <?, ?>) JSON.decode(new String(decode));
final var appleId = (String) map2.get("sub");
```

## 2-5. Tokenの削除
iOSのアプリを開発している場合は、Appleの発行したTokenの削除機能も合わせて用意する必要があります。
パラメータ、レスポンスについては「[Revoke tokens](https://developer.apple.com/documentation/sign_in_with_apple/revoke_tokens)」に記載があります。

```
https://appleid.apple.com/auth/revoke
```

主には、`client_id`、`client_secret`、`token`の項目の指定が必要になります。
`client_secret`は、前に説明しておいたので、書きませんが、`token`については、Tokenエンドポイントで取得した`refresh_token`を指定する必要があります。

# 最後に
実際、リジェクトから始まり、アプリの最新化のためにWeb側にもAppleログインを実装して、解除処理とかも実装して、いろいろと改修の幅が、広がってしましましたが、なんとかアップデートはできました。

Web側の改修はいろいろと手を入れる部分が多いので、こういった体験は記事に残しておくと誰かの参考になればと思います。

こちらのサービスもよろしくお願いします。

https://cotogoto.ai/


# 参考サイト
https://qiita.com/mesummery/items/195b59adc7783d8635d8

http://wiki.genexus.jp/hwiki.aspx?GAM+%E3%81%AE%E8%AA%8D%E8%A8%BC%E3%82%BF%E3%82%A4%E3%83%97%3A+Apple

https://developer.apple.com/forums/thread/127822