オルトプラスエンジニアの日常をお伝えします!

iOSアプリの移管方法:SignInWithApple編

みなさん初めまして、サーバーサイドエンジニアの田宮(id:daiki-tamiya)です。

諸事情により更新を停止していたテックブログですが、約3年ぶりに更新を再開することになりました!今後は順次記事投稿をしていく予定です。

再開後の第一弾として SignInWithAppleによるログイン認証を使用したiOSアプリの移管方法 についてお話ししたいと思います。

はじめに

記事として取りまとめた経緯としましては
・SignWithAppleを導入したアプリの移管事例が少ない
・国内で記事として取りまとめているサイトが少ない
という点から今後需要が出てきそうなので取りまとめることにしました。

また、今回初めてSignWithAppleを導入したアプリの移管検証を行ったのでその際にハマりやすいポイントや気を付けることなども合わせて記載しておりますのでチェックして見て下さい!

※ Firebase AuthenticationによるSignInWithApple認証はメールアドレス管理のため
  特にこちらの記事の対応は必要ないので割愛します。


SignWithApple導入アプリを移管すると何が変わるのか?

基本的にiOSアプリは「AppleDeveloperアカウントA 」から 「AppleDeveloperアカウントB」にアプリを譲渡しても問題無くアプリを引き続き使用できます。ただ、移管した際にSignInWithAppleで使用している一部パラメータに変更が掛かります。

それが

ASAuthorizationAppleIDCredential

ASAuthorizationAppleIDCredential 
var user: String // これ

ユーザー識別子(user)はTeamIDに紐づいたパラメータとなるため、アプリ移管によってTeamIDが変わると次回SignInWithAppleによるログイン時にユーザー識別子は別の値で返ってきます。

つまり、サーバー側でユーザー識別子を保持している場合にアプリ側とサーバー側で異なるユーザー識別子となるため更新する作業が必要となります。


アプリ移管手順

これから説明する移管手順はAppleの公式ドキュメントに沿って対応しています。

SignWithApple Transfers Across Teams
- bringing_new_apps_and_users_into_your_team
- transferring_your_apps_and_users_to_another_team

手順1. client_secret作成
手順2. access_tokenを作成
手順3. 各ユーザーごとにTransfer IDを作成
手順4. 移管後に使用するユーザー識別子をAppleから取得
手順5. ユーザー識別子のCredentialStateの値を確認して再ログインさせる

こちらの手順はアプリ移管後に実行する必要があります。 ただし、手順内で記載されているバッチファイルやアプリ側の改修は事前に準備するようにして下さい。
それでは各手順についての説明していきます。


手順1. client_secret作成

access_tokenを作成するため、まずは取得に必要なclient_secretを作成します。

Generate and Validate Tokens

# 手順1~3で使用する定義
require 'net/http'
require 'json'
require 'jwt'
require 'time'
require 'openssl'

# create client_secret
rsa_private = OpenSSL::PKey::EC.new(File.read(P8_FILE_NAME))
payload = {
    iss: '移管先のTeam ID',
    iat: Time.now.to_i,
    exp: (Time.now + (5 * 60)).to_i,
    aud: 'https://appleid.apple.com',
    sub: 'アプリのバンドルID'
}
header = {kid: '移管先のKey ID'}
client_secret = JWT.encode(payload, rsa_private, 'ES256', header)

ここでハマりやすいポイントとしては「移管先のKey ID」となります。
iOSアプリを譲渡すると
「BundleIdentifier」はそのまま移管先AppleDeveloperアカウントに移行されますが
「SignWithAppleで使用するKey」は移管されません。

アプリ移管後に改めてKeyを作成するようにして下さい。
Key作成方法についてはこちら:「Appleでサインイン」の秘密鍵を作成する


手順2. access_tokenを作成

client_secretを使用して、access_tokenを作成します。

TokenResponse

# request access_token
uri = URI.parse('https://appleid.apple.com/auth/token')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
params = URI.encode_www_form({
    grant_type: 'client_credentials',
    scope: 'user.migration',
    client_id: 'アプリのバンドルID',
    client_secret: client_secret
})
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
res = http.post(uri.path, params, headers)
token = JSON.parse(res.body)['access_token']

手順3. 各ユーザーごとにTransfer IDを作成

access_tokenを使用して、各ユーザーごとにAppleからTransfer IDを取得します。
Transfer IDとは、アプリ譲渡後に使用するユーザー識別子を作成するためのIDです。

今回使用するアプリユーザーIDはテスト用のjsonファイルに用意してから取得していますが、実際はSignInWithApple用のテーブルから取得します。

Generate the Transfer Identifier

# アプリユーザーIDの情報を入れたファイルを用意
users = JSON.load(File.read('data.json'))

# request transfer_sub
uri = URI.parse('https://appleid.apple.com/auth/usermigrationinfo')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    Authorization: "Bearer #{token}"
}
tsubs = users.map do |user|
    params = URI.encode_www_form({
        sub: user[5], # Appleから取得したユーザーID
        target: '移管先Team ID',
        client_id: 'アプリのバンドルID',
        client_secret: client_secret
    })
  res = http.post(uri.path, params, headers)
  data = JSON.parse(res.body)
  [user[0], data['transfer_sub']] 
end

# アプリユーザーIDとユーザー識別子をjsonファイルに保存
File.open('./subs.json', 'w') do |w|
    w << tsubs.to_json
end

手順4. 移管後に使用するユーザー識別子をAppleから取得

Transfer IDを使用してAppleからユーザーIDを取得します。
実際は、ユーザー識別子を取得した後に、DBのSignInWithApple用のテーブルを新しいユーザー識別子で更新します。

Exchange Identifiers

# ユーザー識別子とアプリユーザーIDを取得
subs = JSON.load(File.read('./subs.json'))

# request access_token
uri = URI.parse('https://appleid.apple.com/auth/token')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
params = URI.encode_www_form({
    grant_type: 'client_credentials',
    scope: 'user.migration',
    client_id: 'アプリのバンドルID',
    client_secret: client_secret
})
res = http.post(uri.path, params, headers)
token = JSON.parse(res.body)['access_token']

# request user_id
uri = URI.parse('https://appleid.apple.com/auth/usermigrationinfo')
headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    Authorization: "Bearer #{token}"
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
users = subs.map do |sub|
  params = URI.encode_www_form({
        transfer_sub: sub[1], # Appleから取得したTransfer ID
        client_id: 'アプリのバンドルID',
        client_secret: client_secret
  })
  res = http.post(uri.path, params, headers)
  uid = JSON.parse(res.body)['sub']
  [sub[0], uid] #アプリユーザーID、ユーザー識別子
end

手順5. ユーザー識別子のCredentialStateの値を確認して再ログインさせる

最後にアプリ側で移管後のユーザー識別子を取得するためにSignWithAppleで再ログインさせる必要があります。 CredentialStateの値に応じて再ログインさせ最新のユーザー識別子を利用するように設計して下さい。

指定 CredentialState
移管前のユーザー識別子(userID)が指定された場合 .transferred
移管後のユーザー識別子(userID)が指定された場合 .authorized
ASAuthorizationAppleIDProvider().getCredentialState(forUserID: userID, completion: {
    credentialState, error in
   
    switch(credentialState){

    case .authorized:
    // 移管済みのユーザー識別子はここに入ります
    break

    case .transferred:
    // 移管前のユーザー識別子はここに入ります(再ログインさせる)
    break

    default:
    break
    }
})

再ログイン後のユーザー識別子について1点だけ気を付けることがあります。
それがアプリ譲渡後すぐに再ログインした場合、移管前のユーザー識別子(userID)が返ってきます。

なので、再ログインのループに入ってしまうためアプリによってはメンテナンス等を挟んでユーザー識別子が移管後のものになったのを確認してからサービスを再開するようにして下さい。
今は早く反映されるのかもしれませんが検証時は8時間くらい経ってから新しいユーザー識別子が返ってきましたので余裕を持たせた方が良さそうです・・!
(検証時:2020年 8月頃)

以上でアプリ移管対応は完了となります。


まとめ

  • アプリ譲渡前、譲渡後でユーザー識別子が変わる
  • サーバー側でアプリ譲渡前のユーザー識別子を保持している場合は、バッチファイル等を用意してアプリ譲渡後のユーザー識別子に更新する必要がある
  • アプリ側はSignWithAppleによる再ログインでユーザー識別子をアプリ譲渡後のユーザー識別子に更新する必要がある

おわりに

久しぶりの再開となり第1弾としてはアプリ移管のノウハウを記事とさせていただきました。 今後もaltplus Tech Blogでは新技術や技術的なノウハウなどの記事を定期的に更新していきますので皆さんお楽しみに!

エンジニアに囲まれて働く営業

※Qiitaに書いた記事を転載しています。

メリークリスマス!!

今日はクリスマスイブですね!! 日曜日なのでご友人・ご家族・恋人とお過ごしの方が多いのではないでしょうか。

さてさてAltPlus Advent Calendar24日目、そして初投稿の@h_shiopanです。 私は仕事&おうちで過ごしております・・・。

今日はエンジニアリング的なことではないので箸休め的な記事として ご覧いただけますと幸いです。

簡単な自己紹介

実は私はエンジニアではなく、エンジニアの中で一人働くセキュリティの営業女子(半熟女)です。 情報シスのお仕事をしながら営業をやっているというようなところです。

ひょんなことから、営業行ってみてとCTOに言われ、 現状はセキュリティお姉さん(アカウントマネージャー)という肩書をいただいております。 どうぞ宜しくお願い致します。

エンジニアの中でどう生きているの?

私が主に担当しているのはアプリ開発者向けのセキュリティソリューションです。 開発者向けということもあり、営業する上でエンジニアリング的な知識も必要ですし、 サポートをする上でもエンジアとの連携が欠かせません。 普段エンジニアとはもちろん顔をあわせてのMTGもしますが、集中している時間が多いので 基本的にはSlackを使いコミニュケーションを取っています。 チームのメンバーに恵まれていることもあり、周りは全員男性エンジニアですが、とても快適な環境です。

営業職の大事なこと・心がけていること

完全に私の主観ですので悪しからず。 情シスの仕事をしている時はベンダーさんから営業を受ける側なので 自分が営業職になるとは思ってもいませんでした。(でも後にこの経験がとても大事だったと実感)

営業するにおいて、もちろんエンジニアさんとお話することが多く、一通りの知識が必要となります。 最初はお客様の質問にこたえられないことが本当に悔しくて、悔しくて・・・。 そんな私が日ごろ大事にしていること、心がけていることを書きます。


▶分からないことがあれば恥ずかしがらずにエンジニアに確認する 【聞くは一時の恥、聞かぬは一生の恥】といいますがまさにその通り。 必ずわからないことは確認をします。 きっと恥ずかしい質問をしていることも多々あると思いますが、、、 それが全て自分の知識、お客様への信頼につながります。 自信がつけば台本トークから自分流にカスタマイズできるようになり、お客様との会話にも余裕がうまれました。


▶他の営業職方の対応を学ぶ 私はもともと営業を受ける側だったのでその時の営業さんの対応、 どんな方を信頼していたかを振り返ってみました。 対応速度が速かったり、相談に乗っていただいたりして下さる営業さんには 困ったときなどはこちらから連絡していたことを思い出しました。 また、取引がない時期もちょっとでも顔を出してくれると嬉しいものですよね。 私もそれを見習って定期的にお顔出しさせていただくよう、心がけています。


▶プライベートトークを必ずする 沢山の営業がいる中で【如何に自分のことを覚えてもらえるか】 【困ったときにあの子に頼もうと思って頂けるか】が大切。 営業としての知識、能力はもちろん必要だと思うのですが、 どんなに塩対応を頂いた後でも必ず営業トークの後に、プライベートトークをするようにしています。 それによって笑顔がみれたり、ぐっと距離感が近づくことが実感できます。


▶ノベルティ(ご挨拶の品)を工夫する 年末年始などのご挨拶の際に必要なご挨拶の品。 大半の企業はカレンダーをご挨拶の品にすることが多いのではないでしょうか。 今回私は、他社さんと被らなくて需要があるものは何かと考え、 年末のお掃除にも日頃の掃除にも使っていただけるよう、 デスククリーナーにしました!! IMG_2178.JPG

手作り感満載ですが、お客様にも喜んでいただけてます!!

セキュリティソリューションはどんどんアップデートされ、 学びにゴールはないのですが、これからもエンジニアに助けていただきながら セキュリティソリューションとしてどんどん成長させていきたいと思います。

最後に!

IMG_2384.JPG

上の写真は先日【青の洞窟 SHIBUYA】を通った時に記念に撮影。 とーっても綺麗だったので皆様もお届けいたします。 皆様、良いクリスマスを・・・☆

Whitespaceに触れてみる

この記事はAltplus Advent Calendar 2017の23日目のエントリです。

お疲れ様です。id:kazuha-yamahataです。
今回ですがせっかくのクリスマス前夜なので、それに関連することをやりたいと思います。

クリスマスといえば雪、
雪といえば白、
白は英語でwhite、
whiteといえば・・・Whitespace!!
そんなわけで今回はWhitespaceの紹介をしようかなと思います。

 ※Whitespace (本家: http://compsoc.dur.ac.uk/whitespace/index.php)

今回は実際にコードを見てもらった方が早いと思いますのでコードの説明はいたしません。

そんなわけで早速始めていきましょう。

 
Whitespaceコードを実行するためには、専用のコンパイラが必要になります。
今回はWeb上でコードをコンパイルし実行することができるIdeone(http://ideone.com)で実行の確認をします。 

こちらにサンプルコードを用意いたしました。

f:id:kazuha-yamahata:20171222145049p:plain

 

・・・はいこれがコードとなります。

これを『ideone it!』ボタンを押すことで実行すると。

f:id:kazuha-yamahata:20171222144956p:plain

 

そうです!!!

こちらの言語はメリークリスマスをハローワールドに変換してくれる言語なんです!!!

 


・・・すみません嘘です・・・実際にはこんな感じになってます。

f:id:kazuha-yamahata:20171222145815p:plain

 

以下Whitespaceに関してとなります。 

Whitespaceに関して:
Whitespaceは難解プログラミングの一種となります。難解プログラミング言語は読解が困難なように設計された言語となります。
この言語は[space] [tab] [LF]によって構成されています。つまり何も見えません。
[space]が0 [tab]が1 [LF]が終端記号の役割を持っています。
この[space] [tab] [LF]組み合わせで、スタック操作・演算・ヒープアクセス・フロー制御・I/Oを表現してコードを書いていきます。
このWhitespaceですが、チューリング完全となるため、やろうと思えば何でもできます。やろうと思えば・・・


そんなわけでこのWhitespaceですが、[space] [tab] [LF]でコードが構成されています。

・・・とすると先ほどコード中にあったメリークリスマスはどうなるの?

という疑問があるかと思いますが、このWhitespaceでは白いもの以外は全てコメント扱いとなります。

よって、あのメリークリスマスもコメントとなっていたのです!

こんな感じで誰にも気づかれることなく文章にコードを仕込むこともできますので、ぜひ試してみてください!


はい、そんなわけで今回はwhitespaceの紹介でした!
他にも面白いプログラミング言語がたくさんありますので是非触ってみてください!