Sign in with AppleをCLIでも使いたい

Merpay Advent Calendar 2019 の 23日目は、IDPチーム の @kokukuma がお送りします。

はじめに

Sign in with Apple(以降、SIWA)、コマンドラインツールでも使いたくありませんか?使いたいですよね?わかります。特定のAppleID持っている人じゃないと使えないコマンドラインツールとか作りたいですよね。

さてこのようなコマンドを作るためには何が必要でしょうか?ネックになるのは、認可コードをどうやって自分のコマンドラインツールに引き渡すのか?というところです。AndroidやiOSではDeeplinkの仕組みを使って、Redirect経由でアプリに認可コードを引き渡しています。

しかし、OSXではこの仕組みは利用できません。とりあえず、gcloud auth login仕組みを調べてみました。また、SIWAの紹介は随分出ているので説明は省きます。

gcloud auth loginは、どうなってるの?

下記のコマンドを実行すると、ブラウザが立ち上がって、Google認証のページと、認可画面が出てきます。その間、コマンドライン上は、待受状態になっていて、認可が終了すると先に進みます。まぁ想像はつきますが、想像がつかない体で記事を書いているので、一応コードを読んで確認しておきます。

$ gcloud auth login

Google-cloud-sdkはpythonで書かれているので、gcloud コマンドが使える環境なら、コードを確認することができると思います。auth loginコマンドは、lib/surface/auth/login.py から追っていく事ができます。大まかな流れは、以下のような流れになっていました。この流れは、lib/googlecloudsdk/core/credentials/flow.py を見るのが一番わかりやすそうです。

  • 認可リクエストの作成
  • HTTPサーバの起動/ブラウザの起動
  • 認可コードの取得
  • クレデンシャルの取得/保存

f:id:kokuban-kumasan:20191223163258p:plain

認可リクエストの作成

最初に認可リクエストの作成をしています。実行した内容を見ているとわかるように、redirect_uriには http://localhost:8085 が設定されています。

https://accounts.google.com/o/oauth2/auth?
 client_id=32555940559.apps.googleusercontent.com
 redirect_uri=http://localhost:8085/
 response_type=code
 code_challenge=2HFxG2eBeRyivwzPmfZANRmugSOl_XJSEmgAR1kabo8
 code_challenge_method=S256
 access_type=offline
 prompt=select_account
scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/appengine.admin https://www.googleapis.com/auth/compute https://www.googleapis.com/auth/accounts.reauth

HTTPサーバの起動/ブラウザの起動

次に、HTTPサーバをlocalhost:8085で起動し、認可サーバからのレスポンスを受けれる状態にします。これをした上で、ブラウザを起動し、Googleの認可エンドポイントにアクセスしています。このあとユーザが、ブラウザ上でGoogle認証とGoogle Cloud SDKのGoogleアカウントへの認可を行います。

認可コードの取得

認可が終了すると、設定したredirect_uriに認可コードが送られてきます。

"hd": "mercari.com"
"session_state": "********************"
"prompt": "consent"
"code": "(認可コード)"
"scope": "email https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/appengine.admin https://www.googleapis.com/auth/compute https://www.googleapis.com/auth/accounts.reauth openid"
"authuser": "1"

クレデンシャルの取得/保存

認可コードが手に入ったあとは、トークンエンドポイントを叩いて、アクセストークンやリフレッシュトークン、ID Tokenなどを取得して、それをローカルに保存し、デフォルトの認証情報を書き換えています。

https://www.googleapis.com/oauth2/v4/token?
 client_id=32555940559.apps.googleusercontent.com
 grant_type=authorization_code
 code=(認可コード)
 redirect_uri=http://localhost:8085/
 client_secret=(client secret)
 code_verifier=*******************
 scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/appengine.admin https://www.googleapis.com/auth/compute https://www.googleapis.com/auth/accounts.reauth
"access_token": "(アクセストークン)",
"expires_in": 3600,
"refresh_token": "(リフレッシュトークン)",
"scope": "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/appengine.admin https://www.googleapis.com/auth/compute https://www.googleapis.com/auth/accounts.reauth https://www.googleapis.com/auth/userinfo.email openid",
"token_type": "Bearer",
"id_token": "(JWT)"

長々と説明しましたが、肝になるのは、『auth endpointに送るredirect_uriをlocalhostにして、localhostでhttp server立ててauth code受け取る』です。

SIWAで同じことできる?

SIWAでは、redirect_uriに設定できるのは、httpsを使った検証されたドメインだけで、localhostをredirect_uriに設定することはできません。仕方ないのでAppleに設定したredirect先から、更にlocalhostにredirectしました。図にすると以下のような感じです。また、response modeはform_postにしておきました。このresponse modeとは、認可レスポンスの受け取り方を指定するもので、form_postの他に、queryやfragmentがあります。名の通り、queryの場合はクエリパラメータとして、fragmentの場合はフラグメントとして、認可コードが送られます。一方form_postの場合は、POSTで送られるわけですが、認可レスポンスとしてformを含むHTMLが返され、JavaScriptでformを送信します。

f:id:kokuban-kumasan:20191223163317p:plain

おわりに

コマンドラインでも、SIWA使うことができました。これで、特定のAppleID持っている人じゃないと使えないコマンドラインツールでも作ることができます!本当に必要になるケースは極めて稀な気がしますが、もし必要になった人がいたら是非教えて下さい!

明日のMerpay Advent Calendar 執筆担当は、@Yhkwkmさんです。引き続きお楽しみください。