🔖

Capacitorで実行したSign In with AppleをNodeで無効化する方法

2022/10/31に公開

アカウントの削除機能に関する要件の適用が6月30日開始 というアナウンスが2022年5月にありました。運用がどうなってるのか(実は猶予期間があったのか)わからないのですが、私は2022年10月から当該要件(Sign In with Appleのトークン無効化が実装されていない)でアプリがリジェクトされました。

私は @capacitor-community/apple-sign-in とFirebase Authenticationを組み合わせて使ってるので当該機能ないのかなと調べたのですが、こちらの実装はまだ(早くて2023年Q1にリリース)ないようです。

https://github.com/firebase/firebase-ios-sdk/issues/9906#issuecomment-1285939323

仕方ないから自分で実装するかーと思って、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つの情報を得ます。

  1. PrivateKey
    ダウンロードするファイルに書かれてる -----BEGIN PRIVATE KEY----- からはじまる秘密鍵です。再ダウンロードすることはできないので、必ずダウンロードしておくようにしてください。

  2. 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