Sign in with AppleをWebに導入し, サーバ側実装とメール送信

# はじめに
Sign In with Apple(アップルサインイン)をWebページに導入する方法を解説します．現在，SNSログインを導入しているiOSアプリはアップルサインインの導入が必須となる方針であるため，必然的にアプリに連動したWebページへの導入も必要になるかと思います．その際の導入の参考になれば幸いです．

この記事では以下について触れます．

- SignInWithAppleを導入するための事前登録準備
- Webサイトにアップルサインを導入する手順
- アップルサインインを行うためのサーバ側の処理実装
- 匿名メールアドレス(xxx@privaterelay.appleid.com)にメール送信を行う準備

# 事前準備
## Apple Developer Programに登録

アップルサインインを導入するには、[Apple Developer Program](https://developer.apple.com/jp/programs/)に有償登録(11,800円+税)する必要があります。すでにAppストアでアプリを配布されている方は登録済みかと思います．まだ登録が終わっていない方は余裕を持って登録しておきましょう．．登録審査に最大48時間もしくはこれ以上かかる場合があります．(自分が登録したときはこれ以上かかっていたため、電話問い合わせを行って対応してもらいました。)  

登録審査完了後の画面が表示されていたら次に進みます．

登録審査中:
![image (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/f5efe815-18c6-ae72-af57-088150843a69.png)

登録完了後:
<img width="596" alt="スクリーンショット 2019-11-25 13.36.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/00f64219-dc6c-71d5-54d3-ed6234d3f2a6.png">

## 識別子登録
Web上にアップルサインインを導入するには，事前に関連するアップル社OS向けのアプリ(iOS, macOS,tvOSなど)の登録が必要のようです．まだ登録していない場合は，次の「新規アプリの登録」を行います．すでに登録済みの場合は次の「Web用に登録」に進みます．

### 新規アプリの登録
[Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list)の「Identifiers」を開き、「Register an App ID」もしくは[+]ボタンから新規アプリケーション登録、もしくは既存のアプリケーションを選択して次に進めます．既存のアプリケーションを選択する場合は，表示項目が「App IDs」になっていることを確認します．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/7879bee0-7c2c-6088-90e7-b13dd7f855fe.png)

次の画面では「App IDs」を選択して作成を進めます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/910dfae5-f2fc-87c2-2663-564f7825dcf4.png)

「Sign In with Apple」を選択し，「Configure」をクリックします．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/11db3351-19cd-3f1d-3e70-89293e89302b.png)

表示されたダイアログで「Enable as primary App ID」になっていることを確認して「Save」します．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/170276e9-d701-68c5-80d9-65a1c6c6593c.png)

これでiOSアプリでアップルサインインを導入する登録ができました．
続いて，このアプリに紐付いたWebサイトでアップルサインインが利用できるように設定を行います．

## Web用に登録
[この手順の公式ドキュメント](https://help.apple.com/developer-account/?lang=en#/dev1c0e25352)も併せてご覧ください．

### サービスの登録

[Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list)の「Identifiers」を開き，「Register an App ID」もしくは[+]ボタンから新規サービスを登録，もしくは既存のサービスを選択して次に進めます．既存のサービスを選択する場合は表示項目が「Services IDs」になっていることを確認します．(おそらく「App Ids」になってます．)

次の画面では「Services IDs」を選択して作成を進めます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/908410e5-79fb-667f-3773-38e8cca581c6.png)

Sign in with Appleを選択して「Configure」をクリックします．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/71a2a2ec-6d3c-a59d-c8e2-71f41f41bc48.png)

表示されたモーダル画面の指示に従って必要事項を入力します．

![2019-11-25 15.52.43 developer.apple.com 1b50c5876b4a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/8c3dd0af-6df8-f654-a73a-fc26f4e1f142.png)


- Primary App ID
    - 「新規アプリの登録」で作成したID，または関連するアプリのIDを選択します．
- Web Domain
    - サービスを動かすドメインを指定します．次項で示す方法でドメインの所有証明が必要です．
- Redirect URLs
    - アップルID認証が成功したときの遷移先を指定します．POSTでブラウザがリダイレクトされます．
    - 複数登録できます．
    - Web Domainと完全に同じドメインである必要はないようです．    

いったんこのまま「Save」でダイアログを閉じて，画面を進めてサービスの作成を完了させましょう．
このあと，Web Domainに入力したドメインの所有証明を行います．

### ドメインの所有証明

再びサービスのSign In with Appleの設定画面を開いてドメインの所有証明を行います．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/38034d47-8674-26da-27b3-1eaa644243e3.png)

「Download」て取得したテキストファイルを指定されたURL「https://<ドメイン>/.well-known/apple-developer-domain-association.txt」でGETできるように設置します．(必然的にWebサーバかそれと同等の環境がいる．)設置できたら「Verify」ボタンを押して承認されれば完了です．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/363d7d46-e64a-aa17-d051-a2515ed32cdf.png)

# Webサイトにアップルサインインを導入
## アップルサインインボタンを設置する
それではWebサイトにアップルサインインのボタンを設置していきましょう．
[公式ドキュメント Sign in with Apple JS](https://help.apple.com/developer-account/?lang=en#/devde676e696)も併せてご覧ください．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/1cc965f9-8e9f-de6a-f759-d3b0f995c5ca.png)

Webページ上にアップルIDでサインインボタンを設置する方法は至ってシンプルです．[公式](https://developer.apple.com/documentation/signinwithapplejs/configuring_your_webpage_for_sign_in_with_apple)にも記載がある通り，以下のいずれかの方法をHTMLにきさいすれば完了です．

1つ目:

```html
<html>
    <head>
        <meta name="appleid-signin-client-id" content="${CLIENT_ID}">
        <meta name="appleid-signin-scope" content="${SCOPES}">
        <meta name="appleid-signin-redirect-uri" content="${REDIRECT_URI}">
        <meta name="appleid-signin-state" content="${STATE}">
    </head>
    <body>
        <div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div>
        <script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
    </body>
</html>
```

2つ目: こちらは1つ目でヘッダタグ内で指定した項目をJavaScriptで指定しています．

```html
<html>
    <head>
    </head>
    <body>
        <script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
        <div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div>
        <script type="text/javascript">
            AppleID.auth.init({
                clientId : '${CLIENT_ID}',
                scope : '${SCOPES}',
                redirectURI: '${REDIRECT_URI}',
                state : '${STATE}'
            });
        </script>
    </body>
</html>
```

指定しているパラメータは以下のとおりです．

- CLIENT_ID
    - アップルの開発者向け画面で発行したIdentifierを指定します．
    - 例: "com.your_organization.app"
- SCOPES
    - ユーザの情報のうち取得したいものを指定します．(email:メールアドレス, name:名前)
    - 複数したい場合はスペース区切り．
    - 例: "email name"
    - nameを指定しても取得できるのはユーザの初回登録時のみで以降は取得できません．
    - emailを指定するとユーザのメールアドレスを毎回取得できます．
        - アップル画面でメールアドレスの非公開をユーザが選んだ場合は匿名メールアドレスが返ります．
- REDIRECT_URL
    - アップルID認証成功時にアップル画面から戻ってくるページを指定します．
    - このページにブラウザはPOSTでリダイレクトされます．
    - アップルの開発者画面の「Redirect URLs」で指定したURLのみ有効です．１つだけ指定します．
- STATE
    - 任意の文字列．アップルID認証成功時リダイレクトする時に全く同じ文字列が返ります．
    - CSRFトークンをしていするのが一般的のようです．

## アップル認証成功時の処理を書く．
### リダイレクトとその内容
ユーザがアップルIDサインインボタンをクリックして，アップル認証が成功するとREDIRECT_URLで成功したURLにWebブラウザがPOSTでリダイレクトされます．その際，リクエストボディには認証したアップルIDの情報が含まれています．この情報を使って自サービスのアカウント登録またはログインを行います．

リクエストボディはJSONで以下の情報が含まれています．

```json
{
   "state": "<サインインボタンに指定したSTATE値>"
   "id_token": "<アップルID情報を含むJWT>"
   "user": "{\"name\":{\"firstName\":\"名前\",\"lastName\":\"名字\"},\"email\":\"メールアドレス\"}"
}
```

**state**にはアップルサインインボタンに指定したSTATE値がそのまま含まれています．一般的にはCSRF対策に利用するようです．stateに含まれる値が自身のWebサイトが発行したものかどうかを確認することでCSRF対策に役立てましょう．

**id_token**は，アップルIDの情報が含まれます．ここに含まれる情報を使ってユーザ情報登録もしくはログインを行いましょう．形式は[JSON Web Token(JWT)](https://ja.wikipedia.org/wiki/JSON_Web_Token)です．この値はアップルによって署名されているはずですので，後述する方法で本当にアップルが発行した値か否かを検証するすべきです．

id_tokenを使って2つのことをします．

- 署名を確認して，本当にアップルが発行したJWTであることを確認します．(重要)
- ペイロードからアップルIDのプライマリキーやその他の情報を取得します．


**user**は，初回登録時のみ存在する項目です．2回目以降のログインでは存在しません．SCOPESで指定した項目のみが含まれています．

### stateの検証

リクエストボディに含まれるstate値を使って，自サービスのWebサイトを経由してアップル認証したのかを検証しましょう．いわゆるCSRF対策を行います．ここでは割愛します．

### id_tokenの検証

id_tokenの署名を検証して，アップルが発行した値なのかどうかを確認します．

id_tokenのJWTはRS256方式で署名されています．JWTはRS256方式とHS２５６方式があり，前者が公開鍵・秘密鍵の署名で後者が共通鍵の署名です．(このあたりは[RS256 と HS256 ってなにが違うの](https://qiita.com/satour/items/e68afe3de6267cebcfea)が詳しい．)

id_tokenはRS256方式であり，その鍵は https://appleid.apple.com/auth/keys にて公開されています．この鍵を使って正しく署名されているかを確認します．

具体的にはコード(node.js)は[Unity BlogのSign In with Apple](https://blogs.unity3d.com/jp/2019/09/19/support-for-apple-sign-in/)に掲載されています．抜粋すると以下の部分がポイントです．

```javascript
import jwt from "jsonwebtoken"
import jwksClient from 'jwks-rsa';

// ．．．．

jwt.verify(req.body.id_token, getApplePublicKey, null, (err, decoded) => {
    if (err) {
        console.error(err)
    }
}) 

function getApplePublicKey(header, callback) {
    const client = jwksClient({ jwksUri: "https://appleid.apple.com/auth/keys" })
    client.getSigningKey(header.kid, function (err, key) {
        var signingKey = key.publicKey || key.rsaPublicKey;
        callback(null, signingKey);
    });
}
```

この署名検証でid_tokenがNGであれば，不正なトークンとして処理を中断すべきでしょう．なお，この署名の有効期限は６００秒です．

### id_tokenの解析

id_tokenの署名確認がOKだったら，次はid_tokenが持つペイロードに着目しましょう．ペイロードの内容は https://jwt.io に値を入力して確認することもできます． 上記のコードではdecoded変数にその値が格納されます．

[Unity BlogのSign In with Apple](https://blogs.unity3d.com/jp/2019/09/19/support-for-apple-sign-in/)に各々の項目の概要が説明されています．

特に着目すべき項目は次の２つでしょう．

- sub: あなたのサービスに対するアップルIDのプライマリキー
- email: ユーザのメールアドレス．
    - SCOPESでemailを含んでいた場合のみ存在します．
    - 匿名メールアドレスの場合あり．あなたのサービスに対してユニークなメールアドレス
- aud: あなたのサービスまたはアプリのIdentifier
- iss: https://appleid.apple.comで固定。

**sub**はあなたのサービスにおけるアップルIDユーザのプライマリキーです．従って，この値を使ってユーザを一意に特定できるものとして扱って良いでしょう．同一のアプリ・サービス内であれば同じ値です．もしユーザがアップルID連携を解除して，再度連携した場合でも同じアップルIDユーザであれば同じ値になります．

**email**はユーザのアップルIDメールアドレスです．ただし，SCOPESでemailを指定した場合のみ存在する項目のようです．アップルID本家のメールアドレスである場合と匿名メールアドレスである場合があります．匿名メールアドレスあったとしても，サービス内で同一ユーザは同一のメールアドレスとなります．もしユーザがアップルID連携を解除して，再度連携した場合でも同じアップルIDユーザであれば同じ値になります．

**aud**はあなたのサービスまたはアプリのIdentifierが入っています。適切な値か確認するようにしましょう。

**iss**は発行主がアップルであることを示しており、値は固定です。"https://appleid.apple.com"という文字列が入っていることを確認しておきましょう。

id_tokenのsubはユニークですのでアカウントの作成やログインに役立てるといいかと思います．

# 匿名メールアドレスにメールを送信する．

アップルが発行する匿名メールアドレスにメールを送信すると，ユーザの本来アップルIDメールアドレスに転送されます．しかしながら，メールの送信元を適切に設定していないと，メールを送信しても弾かれてしまい，メールを届けることができません．メールを届けるためには以下のいずれかの操作が必要です．

- メールアドレスのドメインを登録を登録する．
- 送信元メールアドレスを登録する．

ただし，いずれの場合もドメインにSPFレコード登録が必要です．

これについて公式のドキュメントも併せてご覧ください．

- https://developer.apple.com/documentation/signinwithapplejs/communicating_using_the_private_email_relay_service
- https://help.apple.com/developer-account/?lang=en#/devf822fb8fc

登録は，これまで通り開発者向けの画面から左メニューの「More」から行えます．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/c621d0e2-d9be-498c-6863-39472b1d24eb.png)

# さいごに

Sign In with AppleをWebに導入するために必要なことをざっくり記載しました．