概要
2019年9月12日から、新規でApp Storeに提出されるアプリかつ、ソーシャル(サードパーティ)ログインを実装しているアプリにはSign in with Apple
の実装が必須になっているそうです。
https://developer.apple.com/news/?id=09122019b
Starting today, new apps submitted to the App Store must follow these guidelines.
Apps that exclusively use a third-party or social login service (such as Facebook Login, Google Sign-In, Sign in with Twitter, Sign In with LinkedIn, Login with Amazon, or WeChat Login) to set up or authenticate the user’s primary account with the app must also offer Sign in with Apple as an equivalent option.
唐突すぎて信じがたいですが英文を何度読んでもそういう状況だということで、いろいろ調べてみましたのでその情報をまとめます。
背景としては、私自身が自社でiOSアプリを開発中で、既存Webサービスのアプリ化のためTwitterログインやメールログインが既に存在し、そのあたりとの連携も実装不可避になっています。
無事にリリースでき安定稼働した暁にはまた記事にまとめようと思いますが、速報として一旦現在分かっていることを記事にまとめさせていただきました。
既存サービスへの追加のため、既存ユーザーがSign in with Apple
に乗り換えたときのアカウントのIDを連携するようなユースケースに置かれた方々に参考になれば、と思って書きました。
想定読者
- 現在すでに運用しているWebサービスのアプリ版をリリースしようとしているアプリエンジニア・サーバーサイドエンジニア
- およびそれと近しい状況に置かれている方
-
Sign in with Apple
の仕組みなんもわからん、という方
役立った資料
一応公式
サーバーサイドでSign in with Apple
したユーザーのID連携をするときは、このあたりが公式ドキュメントになります。
https://developer.apple.com/documentation/Signinwithapplerestapi/verifying_a_user
https://developer.apple.com/documentation/Signinwithapplerestapi/authenticating_users_with_sign_in_with_apple
しかし情報がなさすぎて正直意味がわからないため、常にブラウザの固定タブで開いておきつつ、以下に示していくような非公式のWebサイトを順次巡りながら、公式に書かれていることと照らしていくような進め方を推奨します。
全体の流れ
バックエンドサーバで管理しているIDと、Sign in with Apple
で得たIDをどうやって連携するかの全体的なフローがシーケンス図で書かれています。
フローの考え方としては、
1. クライアントアプリでSign in with Apple
してAuthorization Code
およびID Token(JWT)を取得
2. サーバーサイドでAuthorization Code
またはID Tokenを検証し、成功すればログイン処理を行う
となります。いくつかやり方が提示されていますので、好みというか要件に沿って選べばいいと思います。
プライベートメールアドレスの仕組みについて
上記のスライドには、
そもそものSign in with Apple
の大きな特徴である、**「ユーザーがプライベートなメールアドレスをサービス側に登録し、自身の本当のメールアドレスは隠蔽する」**という仕組みについても書かれています。
これはApple側で、「ユーザーの本当のメールアドレスとランダムに作成した偽のメールアドレスをリレーする」仕組みを用意していて、サービス側に伝えるのは偽のほうにしておくというものです。サービスから通知とかメルマガで偽のメールアドレスに送ると、Appleのメールサーバー側で転送してくれます。
何の意味がある仕組みかというと、普通、ユーザーがサービスを退会してもサービス側でユーザー情報を論理削除するか物理削除するかなんて利用規約次第なところがあって、論理削除の場合はずっとメールアドレスを保持されるためなにかの拍子に流出するリスクなどがあります。それを根本的に防ぐことができる仕組みです。
68枚目のスライドにめっちゃ重要なことが書かれていて、
ユーザーはApple IDの管理画面からサービスとの連携を解除するというのができて、その場合はプライベートメールアドレスは変わってしまうため、再度Sign in with Apple
したときにメールアドレスの更新処理をする必要があります。
要は初回のSign in with Apple
なのか、何回目かのSign in with Apple
なのか、連携解除後の再度Sign in with Apple
なのかで処理が変わってくるわけで、気をつけてテストしないと取り返しのつかないことになりそうです。
必要なシークレットやclient_id
の生成方法
英語ですが、このサイトが非常によく書かれています。
実際にサーバーサイドからAuthorization Code
を検証するリクエストを飛ばそうと思ったら、client_id
と呼ばれるよくわからないIDに、client_secret
というよりよくわからないシークレットを付けて投げることになります。
https://developer.apple.com/documentation/Signinwithapplerestapi/verifying_a_user
そもそもAppleのコンソールからSign in with Apple
をONにする必要があり、その中で発行されるKeyを使ってclient_secret
をJWTで生成するといった特徴的なフローがあります。
それがこの記事にはスクショ付きで一通りまとめてありますので、これに従って作業を進めればOKです。
特にclient_secret
生成が面倒で、秘密鍵やKey ID、client_id
といったいくつかの値を使ってJWTを生成しています。上記記事ではRubyで書いてありますが、ちゃんと動きますのでこのまま利用できます。ありがたや〜
client_secret
はいくつかの値を使ってJWTを作成したものなのですが、有効期限が半年に設定されるのが一般的なため、実運用にあたっては定期的に上記Rubyコードを実行する体制を整え、かつ生成したJWTをどこかセキュアなところに保存しておいてプロダクションのサーバーからその値を読み取るような仕組みが必要かと思います。私はAWS Parameter Storeを使っているので、LambdaでRubyを書いて定期実行でParameter Storeに突っ込むなどの方法を検討中です。
具体的なリクエストやレスポンス
実際にSign in with Apple
時に利用するエンドポイントを利用するときにどんなJWTが返ってきたり、どんなJWTを投げるかという踏み込んだ部分について書いてあります。
あと、私は使いませんでしたが、Auth0についても書いてあるので、Auth0使っている人は参考になるかもです。
ユーザーのメールアドレスや名前を得るタイミング
上記のスライドにはハマりやすいポイントが書いてありまして、
33ページ目の**「認可レスポンスにユーザー自身のメールアドレスや名前が返ってくるのは初回のみ」**ということです。
※Webから実行したときの挙動として確認済み
そのユーザーにとって一度目または連携解除後の初回のSign in with Apple
時のみ、認可エンドポイントを叩いた後のレスポンスにユーザー自身のメールアドレスや名前が含まれているということです。
Sign in with Apple
したユーザーを既存ユーザーと紐付けたいとなれば、メールアドレスで紐付けるのが定番かと思いますが、この初回タイミングを逃してしまえば連携を解除してもらわない限りできなくなってしまうので要注意です(自動で連携するのではなく、プロフィール画面などに連携するボタンがあり、押したら連携するようなUXにすれば対応可能かとは思います)。
まとめ
まずはヤフーさんのスライドを読んで、どういったシーケンス図で実装しようかという目立てをします。
次に、紹介した英語の資料を読みながら、Appleのコンソールで秘密鍵の作成やSign in with Apple
をONにします。
また、**client_secret
**の生成もやってみます。
ここまで終わったら、Sign in with Apple
のボタンを実際にアプリやブラウザに配置して押してみましょう。
ブラウザならこちらの記事、
https://developer.apple.com/documentation/Signinwithapplejs/configuring_your_webpage_for_sign_in_with_apple
アプリならこのあたりが参考になります。
https://developer.apple.com/documentation/authenticationservices
ボタンを押してみると、ブラウザの場合はredirect_urlで指定したエンドポイントへのリクエストに、アプリからならコールバックでのイベントハンドラでAuthorization Code
やID Token、また初回リクエストの場合はメールアドレスや名前が返ってきているはずです。
次に、バックエンドでID連携処理をやるためにAuthorization Code
やID Token、およびメールアドレスや名前を自前で構築したバックエンドAPIに投げます。
※CRSF対策も必要ならよしなにやってください。ID Tokenの検証の場合はただのJWT検証になるため特に必要だと思います。
バックエンドではID TokenのJWTを復号して中身のissとかsubが妥当かチェックする、またはAuthorization Code
を/auth/token
エンドポイントに投げてCodeの検証をします(補足ですが、前述のclient_secret
が必要云々はAuthorization Code
の検証をするパターンの場合のみです)。
Authorization Code
の検証の場合は、リクエストが成功して、かつAppleへのアクセストークンなど含まれた正常なレスポンスになっていることがチェックできたら、ID連携処理を実行して、必要なアクセストークンをアプリ側に返してあげることでログイン処理完了とする、というフローがいいと思います。
最後に
公式ドキュメントの情報が少なすぎて錯乱しましたが、結果、かなりユーザーフレンドリーでセキュアに設計されたいい仕組みだなあと思っています(洗脳されただけかも)。
もし私と同じような状況で実装した方がいらっしゃったらぜひ情報共有しましょう。
Twitter
https://twitter.com/Meijin_garden
また、弊社では中高生が利用する勉強質問サイトを運営しています。週1〜の副業から一緒に開発するメンバーを募集中ですので、Webエンジニア、iOSエンジニアの方はいつでもお問い合わせください!
https://www.wantedly.com/companies/noschool