0

I am trying to check if my app was ever installed on a device using Device Check Api by apple. But it always returns error 400: Missing or incorrectly formatted payload

NOTE: I have already checked existing stackoverflow questions like:

Connecting to Apple Store Connect using Swift (with SwiftJWT) and REST API's - failing with 401

How do I generate a JWT to use in API authentication for Swift app

None of them helped my resolving the issue.

Instead of server side implementation, we are trying to fetch this information from swift code itself.

Below is the code to create a JWT token using SwiftJWT.

func generateJWT() -> String? {
    let myHeader = Header(kid: "*********")
    let myClaims = MyClaims(iss: "*******", iat: Int(Date().timeIntervalSince1970))
    
    var myJWT = JWT(header: myHeader, claims: myClaims)
    
    guard let privateKeyPath = Bundle.main.url(forResource: "AuthKey", withExtension: ".p8") else { return nil }
    
    do {
        let privateKey: Data = try Data(contentsOf: privateKeyPath, options: .alwaysMapped)
        let jwtSigner = JWTSigner.es256(privateKey: privateKey)
        let signedJWT = try myJWT.sign(using: jwtSigner)
        return signedJWT
    } catch {
        print(error.localizedDescription)
    }
    return nil
}

To Fetch Device Token and call apple's Device check api:

private func generateIdentifier(completion: @escaping (_ token: String) -> Void) {
    let device = DCDevice.current
    if device.isSupported {
        device.generateToken { data, error in
            if let token = data?.base64EncodedString() {
                completion(token)
            }
        }
    }
}

func queryDeviceInfo() {
    guard let jwtToken = generateJWT() else { return }
    generateIdentifier { token in
        let router = ServiceRouter<ReferralApi>()
        router.alamofireRequest(.queryBits(deviceToken: token,
                                           jwtToken: jwtToken)) { [weak self] (response) in
            
            
        }
    }
}

Here is what's inside .queryBits(deviceToken: token, jwtToken: jwtToken)

 var url: String {
    switch self {
    case .queryBits:
        return "https://api.development.devicecheck.apple.com/v1/query_two_bits"
        default:
            return "\(baseURL)\(path)"
    }
}

var headers: HTTPHeaders? {
    switch self {
    case .queryBits(_, let jwt):
        let dic: HTTPHeaders = [
            "Accept": "application/json",
            "Authorization" : "Bearer \(jwt)"
        ]
        return dic
    }
}
var parameters: [String: Any]? {
    switch self {
    case .queryBits(let deviceToken, _):
        return [
            "device_token": deviceToken,
            "timestamp": Date().timeIntervalSince1970*1000,
            "transaction_id": UUID().uuidString]
    default:
            return nil
    }
}

Any help would be more than appreciated.

EDIT Here is the screenshot of the request payload:

enter image description here

Amrit Sidhu
  • 1,870
  • 1
  • 18
  • 32
  • There is no point to device check if you aren’t filtering through a server if someone wants to bypass they will just intercept the check – lorem ipsum Jul 11 '23 at 18:54
  • @loremipsum We will be using server side for this implementation later on. It's just a temporary solution at mobile end meanwhile the server gets ready. Our server side is also facing this same issue due to which we thought of trying at frontend. – Amrit Sidhu Jul 11 '23 at 19:08
  • Does `router.alamofireRequest` POST the data in JSON format? The response of 400 suggests that it doesn't. Can you capture the payload that you are sending? – Paulw11 Jul 11 '23 at 21:25
  • @Paulw11 Yes, the data is in JSON format. I am attaching the screenshot for it. – Amrit Sidhu Jul 11 '23 at 22:24
  • The error message is right there in your screen shot! The `timestamp` has a decimal component. Remove that. You must send an integer. – Paulw11 Jul 11 '23 at 23:26
  • @Paulw11 That was one of the reasons. – Amrit Sidhu Jul 12 '23 at 14:01

1 Answers1

0

I was finally able to figure out the issue. My request had the below issues:

  • Missing JSON Encoding
  • Invalid TimeStamp Formatting

Missing JSON Encoding: Since I was using Alamofire for making a request, setting Content-Type to application/json was not enough, I had to change encoding to JSON Encoding. This is basically the major reason for error 400.

Invalid TimeStamp Formatting: This is kind os tricky. Since timestamp is needed in two places. Both accept different formats.

Place 1: When you are trying to pass timestamp in the payload for the header, the iat key should be having timestamp in seconds as an integer value.

let myClaims = MyClaims(iss: "7X56L99KA2", iat: Int(Date().timeIntervalSince1970))

Place 2: When you are trying to pass timestamp in httpbody as request payload, timestamp should be in milli seconds and in integer value.

let postDict : [String: Any] = [
        "device_token": token,
        "timestamp": Int(Date().timeIntervalSince1970)*1000,
        "transaction_id": UUID().uuidString]

Just because of the timestamp, you could get error 401, that could either be invalid timestamp, invalid payload or unable to verify token.

Amrit Sidhu
  • 1,870
  • 1
  • 18
  • 32