AppleIDログインに関する注意点 

# はじめに
この記事はLinkbal_Advent_Calendar_2020の16日目の記事です。:santa: :santa:

最近、「Sign in with Apple」という機能を実装しました。この機能は一年前に導入されたばかりですので、見つかる情報もまだ少ない気がします。ですので、この記事でこの機能について少し説明したいと思います。実装に関する最初からの細かいガイドは簡単に見つかりますので、この記事は１から説明するものではありません。
今回は実装に関して、注意しなければならないこと、情報が見つかりにくいことなどについて説明したいと思います。
**使った言語はgolangです。**

では、始めましょう！ :apple: 

# 注意点

## client_secretの作成
`client_secret`は最初からの設定ではなくて、`private_key`から作成していきます。このキーはApple Management画面からダウンロードすることができます。

```golang
pemBytes := []byte(config.OAuthApplePrivateKey())
block, _ := pem.Decode(pemBytes)
signKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
	return "", err
}
teamID := config.OAuthAppleTeamId()
keyID := config.OAuthAppleKeyId()

// Add claims
claims := jwtgo.MapClaims{}
claims["iss"] = teamID
claims["iat"] = time.Now().Unix()
claims["exp"] = time.Now().Add(time.Hour).Unix()
claims["aud"] = "https://appleid.apple.com"
claims["sub"] = clientID

token := jwtgo.NewWithClaims(jwtgo.SigningMethodES256, claims)
token.Header["kid"] = keyID
secret, err := token.SignedString(signKey)
if err != nil {
	return "", err
}
return secret, nil
```

こちらのコードでは、必要なクレームを含んでES256アルゴリズムを使用してJWTを生成していきます。`teamID`や`keyID`、`clientID`などはApple Management画面で見つけることができます。
Appleが許可している最大の有効期間は6か月間ですが、ユーザーが認証するたびに新しい`client_secret`を生成する場合は、はるかに短い有効期限を使用することになります。
（今回の例で有効期間は１時間です）

## コールバックのメソッド

FacebookやGoogle、Twitterなどのプロバイダーで認証するの場合、基本のコールバックメソッドはGETですが、Appleの場合、氏名やメールアドレスを取得したい（scopeを指定する）場合、`response_mode = form_post`にする必要があります。つまり、メソッドはPOSTになります。:thinking:
特に問題ないですが、プロジェクトの中に複数のプロバイダーのログイン機能を導入するのであれば注意しなければなりませんね。

例えば、

```golang
if c.Request.Method == http.MethodPost {
    // Get state data returned from POST
	state = c.PostForm("state")
} else {
    // Get state data from query
	state, ok = c.GetQuery("state")
}
```

のように、別の処理コードが必要です。

さらに、POSTなら、ブラウザー上でのクロスサイトのクッキーの扱い仕様が影響するかもしれません。例えば、Safariは発生しないですが、Chromeの場合、AppleLoginの画面で数分放置してから、ログインのフローを続けて、コールバックをするとセッションを受け取ることができなくなります。:frowning2: :frowning2:

## ライブラリーのサポート

golangでサードパーティログインの機能を実装する時、[oauth2](https://pkg.go.dev/golang.org/x/oauth2)というライブラリーが非常に便利です。
しかし、このライブラリーはまだAppleログインをサポートしていませんので、Appleログインのエンドポイントを追加する必要があります。

```golang
// oauth2 library has not supported Apple yet, so we need to add this endpoint manually
if provider == "Apple" {
	oauth2.RegisterBrokenAuthHeaderProvider(AppleTokenURL)
}
```

## 取得したユーザープロフィールのデータ

現在、受け取れるユーザープロフィールに関するデータはメールアドレスと氏名だけです。このデータはレスポンスのフォームでリダイレクトURLに送り返されます。それに**初回1度のみ**メール以外の個人情報を受け取ることができます。

```
Note

The user object will only be presented the first time the user authorizes the application.
```

ですので、もし最初の登録時に何かエラーが発生すれば、個人情報を受け取れなくなります。

## 本番で表示されるロゴ

アカウントへのアクセス許可をユーザーに求める際に表示されるロゴは設定されたプライマリアプリから取得されます。現時点では、ロゴをカスタマイズする方法はありません。
このアプリはアプリストアに公開されている必要があります。公開されていない場合は、Appleのロゴが表示されます。

# Server-to-server notification
この機能に関してはほとんどドキュメントがありませんので、少し説明したいと思います。

## 使用事例
Appleによると、現在ケースが４つあります。

- 有効なプライベートメールから個人用のメールへの転送をユーザーが許可する場合
- 有効なプライベートメールから個人用のメールへの転送をユーザーが許可しない場合
- ユーザーがApple Management画面からアプリを削除する場合
- ユーザーがAppleアカウントを削除する場合

## 戻り値

Appleから取得するデータは、エンコードされたJWTです。このJWTをデコードすると、以下のようなJSONオブジェクトが得られます。

```json
{
    "iss": "https://appleid.apple.com",
    "aud": "jp.couplink.dev-test",
    "exp": 1605944146,
    "iat": 1605857746,
    "jti": "Is7ay7M9H4sbyoI3sfub9g",
    "events": {
        "type": "email-enabled",
        "sub": "000299.8fddfe978b4b417eb0da28ab9209e79f.0413",
        "event_time": 1605857738541,
        "email": "i9h4inrqw7@privaterelay.appleid.com",
        "is_private_email": "true"
    }
}
```

最も重要な情報は`events`オブジェクトにあります。

- `type`:`events`の書類
- `sub`:ユーザーID
- `event_time`:ユーザーの行動が行われた日付・時刻
- `email`:ユーザーのメール
- `is_private_email`:メールが共有されなかった場合 -> `true`

また、Appleから返されるデータの`Content Type`は`application/json`であるため、BindJSONメソッドを使用して処理する必要があることに注意した方がいいと思います。

```golang
var jsonData map[string]interface{}
if c.BindJSON(&jsonData) == nil {
    // Handle returned data
}
```

# 終わりに
長くなるので、ここで以上です！新しい機能なので、将来に変更がありそうですね。あなたの問題の解決に役立つことを願っています。初めての記事ですので、修正点などあればご意見いただけたら幸いです。:pray:

# 参考
- https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple
- https://developer.apple.com/documentation/sign_in_with_apple