3

I am trying to transfer users that use "Sign in with Apple" to another team by following the instructions here: Transferring Your Apps and Users to Another Team

I am getting the access token perfectly fine but when I am trying to get the transfer_sub, I always got invalid_request. I am pretty sure the parameters I provide are correct. My concern is the order of the steps.

Here apple spesifically says:

As part of preparing your data for the recipient team, you will need to generate a transfer identifier for all users in your database prior to initiating a transfer.

On the other hand this answer says:

You cannot use the api "/auth/usermigrationinfo" of the URL before app transfer.

There is one opened issue on apple developer forum here but no answer.

So in the end my questions are:

  1. Which steps should be done before and after the transfer?
  2. Can i get the transfer_sub for each user by sending a request to /usermigrationinfo with old team's client_secret even after the transfer?

1 Answers1

0

Getting the transfer Id, which in the documentation refers as transfer_sub is a bit tricky but you can extract it. Here are the steps:

  1. Get the identity Token after sign-in in with Apple:

    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            if let identityTokenData = appleIDCredential.identityToken {
            let identityToken = String(data: identityTokenData, encoding: .utf8)
            // this is explained on the next step
            if let transferId = decodeIDTokenAndExtractTransferId(identityToken) {
                    print("Transfer ID: \(transferId)")
                }
            }
        }
    }
    
  2. Then, we use the identity token to decode it and extract the JSON that contains the transfer id using the following logic:

    func decodeIDTokenAndExtractTransferId(_ idToken: String?) -> String? {
     guard let idToken = idToken else {
         return nil
     }
    
     let components = idToken.components(separatedBy: ".")
     guard components.count == 3 else {
         return nil
     }
    
     let payloadBase64 = components[1]
     guard let payloadData = base64UrlDecode(payloadBase64) else {
         return nil
     }
    
     do {
         if let json = try JSONSerialization.jsonObject(with: payloadData, options: []) as? [String: Any] {
             return json["transfer_sub"] as? String
         }
     } catch {
         print("Error decoding ID token: \(error.localizedDescription)")
     }
    
     return nil
    }
    
    func base64UrlDecode(_ base64Url: String) -> Data? {
     var base64 = base64Url
         .replacingOccurrences(of: "-", with: "+")
         .replacingOccurrences(of: "_", with: "/")
    
     let remainder = base64.count % 4
     if remainder > 0 {
         base64.append(String(repeating: "=", count: 4 - remainder))
     }
    
     return Data(base64Encoded: base64)
    }
    

This JSON has the following keys inside it where you can get the value that you need:

iss, sub, aud, iat, exp, nonce, nonce_supported, email, email_verified, is_private_email, real_user_status, transfer_sub

You can read what each one of them does here.

Donat Kabashi
  • 384
  • 4
  • 10