Verifying AppStore IAP receipt with own server: keep getting Status 21002 - ios

I'm trying to verify the receipt for an in app purchase with my own node server. I used the information available on this page:
https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html
I send the receipt-data in a base64 encoded string as the value of the key receipt data. But everytime I get the Status 21002 which means that the receipt-data property if malformed or missing.
Im using the request module for node to send the request and this is what my code looks like:
var payload = {
"receipt-data":receipt_data,
"password":password
}
request.post(
config.appStoreReceiptVerificationUrl,
{
form:payload
},
function(err, response, data_str)
{// handle response here}
This is the data in the receipt_data variable: http://textuploader.com/d0tup
What could be the problem?

Related

How to create a "challenge" for my Cloud Functions server

I'm trying use Apple's new DeviceCheck API to verify that network calls in my app are actually coming from an uncompromised version of my app.
Documentation
After successfully verifying a key’s attestation, your server can
require the app to assert its legitimacy for any or all future server
requests. The app does this by signing the request. In the app, first
obtain a unique, one-time challenge from the server. You use a
challenge here, like for attestation, to avoid replay attacks. Then
combine the challenge with the server request to create a hash:
let challenge = <# A string from your server #>
let request = [ "action": "getGameLevel",
"levelId": "1234",
"challenge": challenge ]
guard let clientData = try? JSONEncoder().encode(request) else { return }
let clientDataHash = Data(SHA256.hash(data: clientData))
Use this hash and the key identifier that you generated earlier to
create an assertion object by calling the
generateAssertion(_:clientDataHash:completionHandler:) method:
service.generateAssertion(keyId, clientDataHash: clientDataHash) { assertion, error in
guard error == nil else { /* Handle the error. */ }
// Send the assertion and request to your server.
}
I'm trying to add this assertion functionality to my Swift function, which is a helper function that calls a Firebase Cloud Function.
I want the assertion object to be passed as data to the Cloud Function, to verify that the Cloud Function is being called from an uncompromised version of my app:
func callFunction(name: String, data: [String:Any?], completion: #escaping (HTTPSCallableResult?, Error?)->()){
var functions = Functions.functions()
functions.httpsCallable(name).call(data){ (result, error) in
completion(result, error)
}
}
(Example of callFunction() being used below):
let data: [String:Any?] = [
"gameId": self.game?.id,
"answer": answer,
"answeredAt": Date().millisecondsSince1970
]
callFunction(name: "answerQuestion", data: data){ res, err in
print("Submitted answer: \(res.debugDescription) | Error: \(err)")
if let err = err {
self.game?.question?.state = .initial
}
}
To generate the assertion object to send to my server (cloud function), it requires me to generate a challenge as stated above. However I'm not sure how to generate this challenge.
Apple says it should be "A string from your server". But I'm not sure what the string should be. Is it meant to be a dynamic string based on the user's UID? A Base64-encoded string of the user ID and a static secret string? And when I try to retrieve this string from the server, the user will just be able to read it as they can see incoming network JSON (I presume I would retrieve the string with a Cloud Function call) - so it seems pointless as it's not a secret string anymore?
Any idea how I can make the challenge work securely?
The point of the challenge is to avoid replay attacks, so it can be any randomised string. A UUID would be fine. It doesn't need to be a secret.
The challenge string is combined with the transaction data and a hash is generated. You send the hash to and you send that to generateAssertion and receive the assertion object. You then send this to your server along with the request data.
Now your server can combine the received request data with the challenge (which it knows, since it sent it to the client initially), generate the same hash and validate the attestation.
The server-side attestation article provides detail on the challenge data:
Provide a Challenge
Every time your app needs to communicate attestation data to your server, the app first asks the server for a unique, one-time challenge. App Attest integrates this challenge into the objects that it provides, and that your app sends back to your server for validation. This makes it harder for an attacker to implement a replay attack.
When asked for a challenge, provide your app with a randomized data value, and remember the value for use when verifying the corresponding attestation or assertion objects sent by the client. How you use the challenge data depends on the kind of object that you need to validate.

Twilio Invalid Access Token Signature (iOS - Swift)

I am using Twilio's latest SDK they released on CocoaPods as of today. I am trying to implement VOIP feature to my app with Twilio Programmable Voice. My backend is .net which also uses the latest release of Twilio Helper Library for C#.
My client code looks like:
fetchAccessToken { (accessToken: String) in
TwilioVoice.register(withAccessToken: accessToken, deviceToken: deviceToken) { (error) in
if let error = error {
NSLog("An error occurred while registering: \(error.localizedDescription)")
}
else {
NSLog("Successfully registered for VoIP push notifications.")
}
}
}
What I get in the console is as following:
voipTestWithTwilio[2431:517236] [ERROR TwilioVoice] Inside register:deviceToken:completion:, failed to register for Twilio push notifications. Error:Invalid access token signature
voipTestWithTwilio[2431:517236] An error occurred while registering: Invalid access token signature
This is the C# code that actually creates the token:
var grant = new VoiceGrant
{
OutgoingApplicationSid = outgoingApplicationSid
};
var grants = new HashSet<IGrant> { { grant } };
var token = new Token(
accountSid: accountSid,
signingKeySid: apiKey,
secret: apiSecret,
identity: identity,
grants: grants
);
return token.ToJwt();
I have been looking for the issue on the internet, nothing helped so far. I have tried contacting them but have not got any response back. I also tried creating new api keys and even a new project for a couple times on Twilio. Can anybody say something about the issue?
UPDATE
I added push notification sid to VoiceGrant and now I’m getting 403 Forbidden.
On Twilio error codes page it is explained as: “The expiration time provided in the Access Token exceeds the maximum duration allowed.” which is definitely not my case. However, I tried passing expiration parameter in Token constructor with various values which didn’t change the result.
The problem is still persisting.
I solved the issue. It was because my server returned the token with quotation mark.
I remember print(token)'ing on client (iOS) to see whether there is encoding issue or something and all I see was a proper token between quotation marks. Since token is a string value, I didn't pay attention to quotation part of it. That's where I was wrong.

In App Purchases - Receipt Validation: status = 21004

I am working on Receipt Validation for a subscription In App Purchase for my application. I am Using SwiftyReceiptValidator files from this github project:
https://github.com/crashoverride777/SwiftyReceiptValidator/tree/master/SwiftyReceiptValidator
to help me with my receipt validation.
in my code I'm using:
SwiftyReceiptValidator.validate(forIdentifier: "MyProductId", sharedSecret: "MyCorrectSharedSecret") { (bool: Bool, dict: [String : AnyObject]?) in
}
when I try to validate my product I'm getting status = 21004 which means incorrect SharedSecret Key. But I am 100% sure that my Shared Secret is correct as I copied and pasted it directly from itunes connect.
Question:
Is This error for sure caused by SharedSecret Key? Or could this be caused by something else?
Output when executing the code above
Receipt found
Starting receipt validation
Receipt validation failed: URL request - Invalid receipt status in json response = 21007
Receipt validation failed: Production url used in sandbox mode, trying sandbox url...
Receipt validation failed: URL request - Invalid receipt status in json response = 21004
Receipt validation failed: Status = 21004
It seems to be fixed right now
https://forums.developer.apple.com/thread/72991

Received webhook notification but with empty data in servicem8

I can successfully register the webhook in serviceM8. By list the webhook subscriptions i got the result like this
[{"object":"job","callback_url":"http://ABC.XYZ/oauth/webhook/m8","fields":["active","payment_processed","uuid","company_uuid"],"active":true}]
But when i try to create new job & approve the job in servicem8, i received the notification from serviceM8 but the data in body is all empty like :
body: { '{"object":"JOB","entry":': { '"payment_processed"': '' } }
Am i missing something in webhook setup ???
Webhook notifications will not include the data record itself, only a reference to the record that has been changed/updated.
Your callback url should be receiving a JSON body similar to the example here (under Handling Webhooks):
http://developer.servicem8.com/docs/platform-services/webhooks/
Once you receive the notification, you will need to request the updated record using the supplied resource_url.

iOS verify app store purchase id by developer

How can I check purchase id which was sent by user to me from his orders list?
For example, he can send something like: M1VYXX7VX7 (as written in his purchases list in appstore) and ask to return his purchase (may be he had deleted his app accidentally),
But when I get order information inside of my code (through SKPaymentTransaction) I have no access to that identifier. Then only ID i have looks like: 1000000020706713.
So is there any ways to validate that purchase ID using information which was sent to me by app store?
Thanks.
Read Verifying Store Receipts in the In-App Purchase Programming Guide. According to the documentation:
To verify the receipt, perform the following steps:
Retrieve the receipt data. On iOS, this is the value of the transaction's transactionReceipt property. On OS X, this is the entire contents of the receipt file inside the application bundle. Encode the receipt data using base64 encoding.
Create a JSON object with a single key named receipt-data and the string you created in step 1. Your JSON code should look like this:
{
"receipt-data" : "(receipt bytes here)"
}
Post the JSON object to the App Store using an HTTP POST request. The URL for the store is https://buy.itunes.apple.com/verifyReceipt.
The response received from the App Store is a JSON object with two keys, status and receipt. It should look something like this:
{
"status" : 0,
"receipt" : { (receipt here) }
}
If the value of the status key is 0, this is a valid receipt. If the value is anything other than 0, this receipt is invalid.
Read the article for more details.

Resources