HOWTO remove device tokens received by Apple APNS feedback - iphone

I am successfully fetching Apple APNS feedback data via PHP. The structure that I am getting (after some processing) looks something like this:
timestamp
device token
My question is how to know which of the device tokens should I remove from my database and stop sending notifications to them.
Regardz,
Mladjo

The timestamp is the crucial element here. The timestamp sent by Apple indicates the last time the push service attempted to deliver a message to the device and found the app to be uninstalled. If the device has re-registered with your service since then there is no need to delete it.
Therefore, every single time your app loads and sends the token to your service, you should log the time in your data store. When you run feedback you should check the time from Apple and compare it to the time you last received an update from the app on the device. If the time Apple sends is newer then the time you received an updated then you should delete (or disable) the device. If the time from Apple is earlier then you do not need to delete it because the user has reinstalled the device since Apple last tried to deliver.

All devices given by feedback are 'failed' and should be removed. No feedback means no devices should be removed. It's covered over on the Apple Documentation:
Apple APN Documentation

neat explanation #argon, however I have another question about the timestamp.
Every time when an app enables push notification, the device token is sent to the server. Should I taken the timestamp from my server as to be persisted along with device token as didRegisterForRemoteNotificationsWithDeviceToken only gives deviceToken and not time. If my server runs in different timezone and APNS is running at different timezone, then the registration time stored( along with device token) cannot be compared with timestamp received from APNS feedback to check the sequence of register -> uninstall -> reregister.
I presume the APNS feedback timestamp is in UTC and the timestamp the server stores along with device token has to be converted to UTC before storing the ISO timestamp. This way both the timestamp will be in same timezone and diff check will be consistent.
please clarify

#fyasar,
So your recommendation is to store the device token against device id(or some key). When a feedback is received for a device token, remove that device token row from DB, right? If have understood right, that wouldn't work in scenario were a user installs app, uninstalls it and then installs it again all with in a short duration and the feedback service was queried only after all this happened. In this case, if timestamp in feedback is not considered, device token will be removed which is incorrect as user has again installed the app and reregistered for push notification.
My question is this, as suggested in apple doc and many blogs, on registration, when device token is persisted, timestamp has to be persisted along with it. What time zone's ISO time should be persisted or what is the time zone on which feedback service returns the timestamp.

A timestamp (as a four-byte time_t value) indicating when APNs
determined that the app no longer exists on the device. This value,
which is in network order, represents the seconds since 12:00 midnight
on January 1, 1970 UTC.
you can compare it with your table's last insert time and then remove the invalid token from db, In my case i am using mysql and php for sever side
$sql="SELECT insert_time from device_tokens ORDER BY insert_time DESC LIMIT 1";
it will return last updated time from db and then i just convert it into epoch timestamp by using
$sql1="SELECT UNIX_TIMESTAMP(' $timestamp')";
and finally i just compare it with apns feedback timestamp like this
if($inactive_Timestamp>$dbTime_stamp)
{
foreach ($apnsfeedback_tokens as $key => $value) {
# code...
$inactive_Token=$value['devtoken'];
$sql= "DELETE FROM device_tokens WHERE device_token='$inactive_Token'";
if ($conn->query($sql) === TRUE) {
echo "Record deleted successfully";
} else {
echo "Error deleting record: " . $conn->error;
}

You should store devices with their device token data, and then you can find these devices according to their device tokens. You might use device token for identifier each device. Than would be easy to find and change their statuses into your db.

Related

Designing Simple Push Notifications System

Need some design advice for on implementing a push notification system. I am using for the backend Symfony 3, Doctrine and postgres as the database. The notification system will have two types of notifications:
1. Instant notifications
2. Scheduled Notifications
Instant notifications are when an action is performed in the backend and a notification is instantly pushed. For example, a notification when a user is deleted in the database.
Scheduled notifications is for a notification that has not been triggered yet but will at some known time in the future. The notification will only be triggered when it reaches that set time. For example an Alarm clock.
Based on this I have designed simple tables as follows with some mock data:
So with above information the API concept is at user’s device such as mobile phone, it will run a GET/notifications API every 2 mins. The API will return notifications that are before the current date and has not been dismissed for that user. For example, if USER2 executes the GET/notifications with the current date at 18th April 2017 it will return record 3.
My question is this type of design acceptable if not what would be the conventional practice? Also with many users running the same GET API every 2 mins is there a potential to slow down the system? I’m guessing about at most 3000 users.
Finally, as the notifications table grows in records will postgres slow down when performing this API?
Who ever has taken the time to read it all the way through thank you, I am very grateful for any advice.
Push Notification.
If your client devices are asking for notifications via a GET API call, Its not called Push notifications. Down the time, it will be heavy on server side to handle too much unnecessary requests as all users might not have that much notifications.
You shouldn't let your clients call. You should push the notifications to client instead. The iOS and Android apps can be configured to get push notifications through deviceId and uuid. Ask your mobile developers for the same. These days even HTML5 browsers are supporting push notifications.
For storage :
I think you don't have to store instant push notifications in database at all, unless you have an application level requirement to store the logs.
You should only store scheduled notifications so that a Cron job can pick and push those notifications when they are due.
Hope this helps!

iOS- Apple push notification resend from APNS

From Apple's document I understand that if the device is offline, the APNS holds the last notification and sends it to the device when the device is back online. Is there is any mechanism to avoid that resending?
The correct answer is YES
If you send notifications using the enhanced binary format (the one that includes message identifier and expiry), you can use the expiry parameter to prevent the notification from being stored in the APN server. This way the notification is delivered only if the device is online when the APN server first tries to deliver it.
Expiry
A fixed UNIX epoch date expressed in seconds (UTC) that identifies when the notification is no longer valid and can be discarded. The expiry value uses network byte order (big endian). If the expiry value is positive, APNs tries to deliver the notification at least once. Specify zero (or a value less than zero) to request that APNs not store the notification at all.
The answer is NO
Here is what apple says about it,
If APNs attempts to deliver a notification but the device is offline,
the notification is stored for a limited period of time, and delivered
to the device when it becomes available.
Only one recent notification for a particular application is stored.
If multiple notifications are sent while the device is offline, each
new notification causes the prior notification to be discarded. This
behavior of keeping only the newest notification is referred to as
coalescing notifications.
If the device remains offline for a long time, any notifications that
were being stored for it are discarded.
I agree with Eran.
This part of your question "the APNS holds the last notification and sends it to the device when the device is back online" is only true if Expiration date is different of zero.
If you do want the message to be delivered when the phone gets online, the message will wait until "Expiration date" before be discarded.
APNS documentation:
Expiration date 4 bytes A UNIX epoch date expressed in seconds (UTC)
that identifies when the notification is no longer valid and can be
discarded. If this value is non-zero, APNs stores the notification
tries to deliver the notification at least once. Specify zero to
indicate that the notification expires immediately and that APNs
should not store the notification at all.
For more details, refer to https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html

Subscription Expiry Date if user changes dates

Our app uses Non-Renewable Subscriptions to provide access to content for one year. The subscription receipt is stored on iCloud (when available) and in the device keychain. There is also a WS that is called on occasion to validate the receipt but does not record the transaction.
Our problem is if the users change their clock backwards (say set the date to 2010), our subscription does not expire after one year but now three years (2013-2010).
Our solution was to say that subscriptions with a duration of more than one year are fraudulent and the receipt is cleared and life goes on.
However, my boss now is complaining about users that set the clock to six months in the past (less than one year so ok, but the subscription lasts for 1.5 years instead of the valid one year).
Is there a better way to be dealing with device clock tampering or is it something we have to live with (as I believe)?
If your app gets content from a server then you could store a list of "device-identifiers" combined with an expiration date.
Being on your server the client-time will have no voice on when the subscription is expired or not.
As I said in my comment above, I would check the time with a trusted server instead of using the local time. Just as important, once a receipt expires, immediately delete it or mark it as expired so that they can't turn off network access and roll back the time in order to use it.

Why registering for push notifications every time a user launch an app?

In the Apple documentation you can find the following sentence :
An application should register every time it launches and give its provider the current token. It calls registerForRemoteNotificationTypes: to kick off the registration process.
So when I implemented the push notification in my app I had to register the device, and I did what they said in that documentation: registering every time a user launch my app.
The token that I receive from the APNS is always the same for a given user.
My question is: why do I need to register everytime if the APNS gives me always the same token?
I read somewhere than a token can change if a user swipe his iPhone or the app. Is it the only case?
Thank you !
The token that I receive from the APNS is always the same for a given user.
Except it isn't, basically because there's nothing you can hang onto as being "a user" in the iPhone setup. The device token is always the same for each app for each device. So different apps on the same device get different tokens. The same app on two different devices gets two different tokens.
The crucial thing to note, and this is mentioned in the APNS guide, is that a user may back up their apps, settings, everything. Then they can drop their phone down the toilet. When they get their replacement phone, they can take their backup and restore it onto their new phone. Bingo - same app, same user, different device, and different token.
As far as your app is concerned, nothing has changed since the last time it ran - it doesn't know that it's actually running on a different device now. The only way it knows is because it asks for the 'current' device token, and hey presto it's a different token to last time.
You can choose to cache the token and check it against the token you just received (e.g. save it in your NSUserDefaults) - that way you don't have to communicate it back to the server unless it has changed since the last run, but you absolutely do have to check, otherwise your users will come complaining that they don't get push notifications any more since they replaced their phone.

How can my server securely authenticate iPhone in-app purchase?

Look at Apple's diagram for the server purchase model.
In step #9, how can the server know that it is really talking with an iPhone that is entitled to the purchase, and that Eve is not performing a replay with a dishonestly obtained receipt?
The receipt may be valid, but that doesn't prove that the sender is the entitled party.
Is there any notion of a device certificate on the iPhone that can be used to sign the receipt?
Is there any way to bind the receipt to the device, or bind the receipt to both the iTunes account and to the device, so the server can validate?
Apple-Provided Vulnerable Approach
The server can authenticate a purchase by doing the following:
The iPhone application receives a transactionReceipt after the purchase. Have the iPhone base64 encode it (You can use this open-source addition to NSData) and send it to your server. (You could even send it as-is and have the server base64 encode it before validation.)
Have your server send a JSON request with the single key receipt-data with the base64 encoded transactionReceipt to https://buy.itunes.apple.com/verifyReceipt using an HTTP POST. (For directions on how to do this in various server-side languages see this site)
The server will respond with a JSON object with two keys: status which is an integer and receipt which is the receipt repeated.
If status is zero, the receipt is valid should be accepted, a non-zero value means the receipt isn't valid.
Secure Additions to Apple's Approach
However, there are a few security implications. A user could use another user's receipt since devices aren't tied to receipts, or a user could use another product's receipt since the server doesn't verify the product id of the receipt. To ensure this doesn't happen you should also do the following:
When you first get the receipt in the application, immediately send it to your server along with the device's UUID over a secure channel such as HTTPS or an SSL socket. Do not store it anywhere, leave it in memory.
On your server, store the UUID and receipt pair in a database.
When a device sends a UUID and receipt pair, verify with your database that the receipt has not already been used, and make sure the receipt is actually for your product by checking the receipt's product id. The receipt is just a JSON object, so your server can read the contents by decoding the receipt from base64.
Return a response to the device over the secure channel telling it whether the purchase is:
Authenticated as new (wasn't in DB and was valid)
Authenticated in the past (Same UUID and receipt pair was already in DB)
Denied due to wrong product id
Denied due to having already used the receipt with another UUID.
Since the receipt is only ever in memory on the device, and your application uses the device's UUID (can be spoofed by jailbroken devices, see comments), and all purchases of your product are logged with the device's UUID on your server in a secure manner; a user could not use another user's receipt to verify the purchase, nor could they use a receipt from another product, since you check for that.
You can also validate other fields from the receipt if you want to verify other details of the transaction. For example, if your product is a subscription, you'll like want to look at the transaction date as well.
Also, users cannot pretend to be your server by having the device on a private network with a host of the same name as yours, since they won't have your SSL certificate.
Failure Considerations
Since failure might occur between when the user's device gets the receipt and verifying it with your server (for example if the user looses connectivity, or your server is down for maintenance), you should also let the user "re-authorize". Re-authorizing should get the receipt from the store (using a Restored Transaction) and re-send it to the server just as though this was a new purchase. This should rarely need to be used, but should be available to save the user having to re-buy the product in the case of network failure.
Multiple Devices Consideration
This means that if a user wants to use an application on more than one device, they will have to purchase the product multiple times. This might be the desired effect, but you should likely inform your users before they purchase since they might expect to be able to use the content across devices they have associated with their account.
If the receipt also contains the iTunes account information, authentication could use that to allow users to share content between all their devices (but not their friends').
I do not think that can bind the receipt to the device.
My understanding is that you are allowed to install an application on multiple devices without extra cost. Binding it to the device would mean that if you for example upgrade/change your phone you would need to purchase all the apps again.
I believe if you can't read the user's apple ID, your only protection against piracy would be keeping track(server-side of course) of the number of download requests per transaction_id and limit those if they go over a certain value.
So if you limit it to say 50, that gives a reasonable margin for the user to deploy the app and it's contents on multiple devices and restore several times, but makes it hard for whoever wants to distribute a pirated version with a valid receipt for unlimited restores. Of course they can just distribute a version with all your content, but that's nothing you can do about that and at least they're not taxing your servers.
UDID Does Not Work Anymore
Beniot answer is great, however, these days, as mentioned by Joe D'Andrea, UDID is deprecated and the last time I tried, an App that used the call to get the UDID failed to pass validation during upload to iTunes.
Time-limited Receipts as Alternative to Receipt Counters
To add on to hloupyhonza's answer, besides having a "download request" counter for a particular receipt, you can just limit the receipt validity by time. I found anything between 12 to 24 hours reasonable.
This method also allows the purchaser to use the purchase on any other device he owns as long as he logs into the App Store with the same Apple ID. Note: Each time Restore Purchases is done, Apple returns a completely new receipt (with details of the original receipt contained) - this allows purchases to be restored past the time limit we set for a particular receipt.
Preventing "Off-The-Shelf" Hacks
To prevent typical "Googled" hacking solutions (my data shows this constitutes almost all of IAP hacking attempts), I use a checksum (pick your favorite algorithm, doesn't matter unless you want to make it watertight) of the following concatenation:
receipt-data json string
A shared secret key
Validation success status code.
The App will verify the checksum returned by our validation server. This is not watertight though, as the hacker may retrieve the shared key from your App's binary. But it has prevented all "off-the-shelf" hacks thus far and that's good enough for my use.