We have a server-side service that we only want to offer to valid users of our paid iOS app. (Note that this is a paid iOS app, not a free app with IAP.)
When we use appStoreReceiptURL
to check the sandbox app receipt and send it to our server side, we see a receipt like this:
{
"receipt_type": "ProductionSandbox",
"adam_id": 0,
"app_item_id": 0,
"bundle_id": "com.example.myapp",
"application_version": "1.1.1",
"download_id": 0,
"version_external_identifier": 0,
"receipt_creation_date": "2018-04-16 23:53:58 Etc/GMT",
"receipt_creation_date_ms": "1523922838000",
"receipt_creation_date_pst": "2018-04-16 16:53:58 America/Los_Angeles",
"request_date": "2018-04-17 03:25:42 Etc/GMT",
"request_date_ms": "1523935542798",
"request_date_pst": "2018-04-16 20:25:42 America/Los_Angeles",
"original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
"original_purchase_date_ms": "1375340400000",
"original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
"original_application_version": "1.0",
"in_app": []
}
I'm concerned about replay attacks with this receipt. In a replay attack, one device purchases the app and submits a valid receipt, but a second unauthorized device stores and transmits an exact copy of the first receipt. Since the first receipt is signed by Apple, the duplicate appears valid.
Ideally, we'd defeat a replay attack by observing a unique identifier in the receipt; if someone attempts to retransmit the same receipt ID, we'd know that it's a duplicate. IAP receipts include a transaction_identifier
field for exactly this reason.
But there appears to be no unique identifier that we can use to recognize replay attacks with paid-app receipts. Hackers can retransmit this receipt to us from different devices and we'll have no way of knowing whether it's a duplicated receipt or a new, original receipt.
Having said this, my eye is drawn to those _id
numbers that are 0 in the sandbox receipt: adam_id
, app_item_id
, and download_id
. Can we use any of those to recognize duplicate receipts? Or is there some other, better way of handling this?