Capacitorで実行したSign In with AppleをNodeで無効化する方法
アカウントの削除機能に関する要件の適用が6月30日開始 というアナウンスが2022年5月にありました。運用がどうなってるのか(実は猶予期間があったのか)わからないのですが、私は2022年10月から当該要件(Sign In with Appleのトークン無効化が実装されていない)でアプリがリジェクトされました。
私は @capacitor-community/apple-sign-in
とFirebase Authenticationを組み合わせて使ってるので当該機能ないのかなと調べたのですが、こちらの実装はまだ(早くて2023年Q1にリリース)ないようです。
仕方ないから自分で実装するかーと思って、Node.jsで実装・リリースしましたので簡単に実装方法についてまとめておきます。
実装
Sign In with Appleの一時トークンを取得する
トークンを無効化するには、トークンが必要です。退会時に「トークンを削除するので再ログインしてください」というのも個人情報を抜くための不審な実装っぽいので、まずは最初にSign In with Appleされた時に得ることができる一時トークンを永続化しましょう。まず、 @capacitor-community/apple-sign-in
が成功すると、 response
というオブジェクトを得ることができます。
const appleLogin: {
response: ResponseSignInWithApplePlugin;
} = await SignInWithApple.authorize();
中身の型はこうなっています。
export class ResponseSignInWithApplePlugin {
user: string;
identityToken: string;
authorizationCode: string;
email: string;
givenName: string;
familyName: string;
}
authorizationCode
が一時トークンです。これをサーバに送って永続化されたトークンを取得し、ユーザがアカウント削除をするまで保持しておきます。
永続化トークンを取得する
appleでPrivateKeyを取得する
https://developer.apple.com/account/resources/authkeys/list で、PrivateKeyを取得します。
Certificates, Identifiers & Profiles > Keys
Keys
の右隣にあるプラスアイコンをクリックし、アカウントを作成します。Key Nameは任意のキー名入力し、 Sign in with Apple
を有効にしてください。ここでは、3つの情報を得ます。
-
PrivateKey
ダウンロードするファイルに書かれてる-----BEGIN PRIVATE KEY-----
からはじまる秘密鍵です。再ダウンロードすることはできないので、必ずダウンロードしておくようにしてください。 -
Key ID と CONFIGURATION
View Key Details
から確認することができます。
Key IDはそのままですね。 CONFIGURATION は、バンドルIDの前につく文字列です。私のこのアプリはバンドルID jp.rdlabo.winecode
なので、その前についてるやつですね(黒塗りしてる部分です)
NodeでJWTを作成する
認証にJWTを使うので、 jsonwebtoken
をインストールします。
% npm install jsonwebtoken
そして、それを利用して、JWTを作成するメソッドをつくります(私はclassベースのNestJSを使ってるので、classの中に書いてます)。
import { sign } from 'jsonwebtoken';
const makeJWT = () => {
//Sign with your team ID and key ID information.
return sign(
{
iss: '●●●●', // CONFIGURATIONに置き換えてください
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 120,
aud: 'https://appleid.apple.com',
sub: 'jp.rdlabo.winecode', // 自分自身のバンドルIDに置き換えてください。
},
'●●●●', // -----BEGIN PRIVATE KEY----- からはじまるPrivate Keyに置き換えてください。文字列で大丈夫です。
{
algorithm: 'ES256',
header: {
alg: 'ES256',
kid: '●●●●', // Key IDに置き換えてください
},
},
);
}
これでApple用のJWTを用意することができました。
JWTを使って永続キーを用意する
AppleのREST APIにPOSTする時は、文字列を使いますので、オブジェクトを文字列に簡単にできるnpmパッケージ qs
をインストールします。
% npm install qs
そして、AppleのREST APIにPOSTします。ここでは @nestjs/axios
を使ってるのでObservableですが、普通のPromiseベースのHTTPクライアント(もしくはfetch)で大丈夫です。
import { HttpService } from '@nestjs/axios';
import { stringify } from 'qs';
const { data } = await firstValueFrom(
this.http.post(
'https://appleid.apple.com/auth/token',
stringify({
code: authorizationCode, // Capacitorで取得した一時トークン authorizationCode
client_id: 'jp.rdlabo.winecode', // 自分自身のバンドルID
client_secret: makeJWT(), // 先程作成したメソッド
grant_type: 'authorization_code',
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
},
),
)
うまくいくと、取得した data
オブジェクトの中に refresh_token
というキー名で永続化されたトークンが入っています。これを保存してください。Firebase Authenticationなどを使ってる場合は、そのサービスのユーザテーブル、それがなければ自前で永続化できるDBを用意しておくといいかと思います。
永続キーを使って、ユーザをRevokeする
いよいよユーザが退会する処理です。といっても永続化されたトークンでAppleのREST APIにPOSTするだけです。
import { HttpService } from '@nestjs/axios';
import { stringify } from 'qs';
await firstValueFrom(
this.http.post(
'https://appleid.apple.com/auth/revoke',
stringify({
token: authorizationCode, // 保存しておいた永続キー
client_id: 'jp.rdlabo.winecode', // 自分自身のバンドルID
client_secret: makeJWT(),
token_type_hint: 'refresh_token',
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
},
),
)
トークンが無効化された場合、そのトークンを使って API を利用できないことはもちろん、iPhone の場合
設定 > Apple ID > パスワードとセキュリティ > Apple ID を使用中の App
のリストの中から Sign In with Apple を利用していたアプリが消えているのが確認できます。
まとめ
まだあまりこの要件でリジェクトされたという話を聞かないのでこれからかなとは思っていますが、CapacitorでSign In with Appleを実装しててリジェクトされた方がいれば、何かの参考になりましたら幸いです。
Discussion