Let's write β

プログラミング中にできたことか、思ったこととか

Auth0でSign in With Appleから氏名がAPIで取得できない件への暫定対応

弊社のアプリケーションではモバイルアプリの認証にAuth0を利用しています。 そして、Auth0からユーザーに利用可能な認証方式の1つとしてSign in with Appleを採用しています。

Sign in with appleを認証で利用していると、さらに追加でユーザーに氏名の入力を求めているとストアの審査ガイドラインで落されてしまいます。 そこで、Auth0側から情報をとりだすようにして入力はスキップさせる必要があります。

本来、Sign in With Appleで認証したユーザーの情報はAuth0に保管されるので、 Auth0のAPIを叩く事でAppleから取得できていたユーザーの氏名等を本来取得できるはずなのですが、現在(日本時間 2021/02/20 2:00)の時点では 氏名の情報を返すパラメータ名がAuth0とAppleの間で同期できておらず、正常に取得ができなくなってしまっておりましたので、調査して暫定の対応をしました。

発生していた事象

Auth0からfamily_name, given_nameがかえってこない

Sign in with AppleでAuth0で認証したユーザーの family_name および given_name をフィールドをサーバーサイドから取得しようとしたのですが、値が返ってこず取得できなくなっていました。

first_name, last_nameというフィールドにははいっているがAPIからは取得できない

Auth0のユーザー一覧を確認しにいってみると, first_name, last_name というフィールドにどうも氏名の情報がはいっているようです。 ではこの値を取得できないのかとしたかったのですが、このフィールドを取得しようとRubyのSDKから叩くとBad Request Errorが発生します

Auth0::BadRequest ({"statusCode":400,"error":"Bad Request","message":"Query validation error: 'String 'first_name,last_name' does not match pattern. Must be a comma separated list of the following values: phone_number,email,email_verified,picture,username,user_id,name,nickname,created_at,identities,app_metadata,user_metadata,last_ip,last_login,logins_count,updated_at,blocked,family_name,given_name' on property fields (Comma-separated list of fields to include or exclude (based on value provided for include_fields) in the result. Leave empty to retrieve all fields).","errorCode":"invalid_query_string"})

たしかに、Auth0のAPIドキュメントを見てもUserのフィールド名としてはfamily_nameやlast_nameというのは存在しません

auth0.com

ではなぜ、Auth0はfamily_name, given_nameという名称に限定しているのだろうという事を調査してみると、 このAuth0のフォーラムでも推測されていたのですが

community.auth0.com

どうも、OIDC標準では氏名のフィールド名はfamily_name given_name とする事となっており、 first_name last_name というのはAppleの独自の仕様のようです

developer.apple.com

おそらくですが、ここの同期というかマッピングがAuth0側でまだ対応されておらず、Auth0には保存されているが読みだす事はできないという状態になってしまっているようです。

暫定対応方法

フォーラムの方法は残念ながらそのままでは動かなかった

上記のフォーラムでは、Ruleの中でAppleから情報を取得した時は、family_name, given_name の値をupdateするという方法はどう? という方法が推奨されています。 しかし、現時点で実際にそのRuleを設定してみると、以下のエラーが発生しました。

{"statusCode":400,"error":"Bad Request","message":"The following user attributes cannot be updated: family_name, given_name. The connection (apple) must either be a database connection (using the Auth0 store), a passwordless connection (email or sms) or has disabled 'Sync user profile attributes at each login'. For more information, see https://auth0.com/docs/dashboard/guides/connections/configure-connection-sync"

エラーを読むかぎりでは、appleからの情報取得で発行されたユーザーの family_name given_name という フィールドの更新はログイン時のプロフィールの自動同期をオフにしない限りは利用できないようです、 名前にかぎらず他のプロフィールの同期の事も考えるとこのためだけに同期を切るというのもいただけないので、他の対応を考える事としました

user_metadataに保管する

そこで、暫定対応としてですが user_metadata というなんでも保管しておけるattributeに目をつけました。 family_name, given_nameを直接更新するのではなく、そちらのメタデータにほうりこんでしまうのです

function (user, context, callback) {

  // you might want to add a check to only run this 
  // if `user.family_name` and `user.given_name` is actually missing
  if (context.connectionStrategy === "apple")  { 
    var ManagementClient = require('auth0@2.32.0').ManagementClient;
    var management = new ManagementClient({
        token: auth0.accessToken,
        domain: auth0.domain
    });
    // persist in user store
    management.updateUser({id: user.user_id}, 
        {user_metadata: { apple_family_name: user.last_name, apple_given_name: user.first_name }})
    .then(function(u){    
      callback(null, u, context);
    })
    .catch(function(err){
      callback(err);
    });     
  } else { // if not apple
    callback(null, user, context);
  }

}

このようにするとエラーが発生する事なく

"user_metadata": {
        "apple_family_name": "Tesuto",
        "apple_given_name": "Taro"
},

とAuth0上に情報が保存される事が確認できました。

サーバーからはフォールバックしながら読みだす

user_metadataフィールドはAPIから読み出す事ができるので、サーバー上で他の認証方式では 正常にとれているはずのfamily_name、given_nameを優先しながら、次善の策としてこのメタデータにフォールバックする形式に変更しました。

user_profile = auth0_client.user(
  auth0_subject,
  fields: "family_name,given_name,user_metadata"
)
family_name = user_profile["family_name"] || user_profile.dig("user_metadata", "apple_family_name")
given_name = user_profile["given_name"] || user_profile.dig("user_metadata", "apple_given_name")

これで、無事にサーバーからApple以外の認証方式とも両立させながら無事情報取得が可能になりました..早めに公式で解決すると良いな...