iPhone app consumables - Server vs keychain method - ios

I had an iPhone app developed by a company, which contains consumables purchases, specifically a currency named as "coins", used for app-specific purchases. In the current version, the team has added the ability for a user to register under a username/email/password. This information is stored on my external server and used as a recovery for the user's purchased coins via signing in using these credentials.
The model looks fine programmatically but is not convenient in terms of usability. Ideally, i would want my users to not have to register at all. I have been reading that it is a possibility to store the user's purchased consumables on the client side, using the internal keychain.
Now this would imply that consumables can only be restored on this single device (i guess unless iCloud is used). But is this allowed by Apple ? Or there should be a method to restore across different devices ?
If the keychain method is allowed, I believe that it would be way better for my app to go with that. I just wanted to make sure that Apple would not have a problem with that. I have been reading that it's allowed, but do not want to have unpleasant surprised after changing the whole client-server model to client-only keychain.
If you have experience with that, could you please let me know what the case is ?
EDIT : Somewhere on SO I read that using Gamecenter's GKPlayer class is also a way to store information on my server and retrieve that automatically in order to identify a user purchase. This would mean that the step of username/email registration is avoided, which is something i really really want to avoid. Is this really a possibility that is allowed by Apple ?
EDIT 2 : I am now thinking that I will keep the client-server model but lose the username/password and use the device id instead. This way i will be able to keep the restoration of coins working on a specific device. Would that be ok you think?

Related

Is it ok to repurpose SKPayment.applicationUsername?

Regarding SKPayment.applicationUsername, it says here...
This is used to help the store detect irregular activity. For example,
in a game, it would be unusual for dozens of different iTunes Store
accounts making purchases on behalf of the same in-game character.
The recommended implementation is to use a one-way hash of the user’s
account name to calculate the value for this property.
This property is optional. So, am I allowed to use it for something else entirely? I want to use it to store details about the product that are not stored in iTunes.
SKPayment.applicationUsername is only used by Apple on the backend for fraud detection. They can use it to see if a single user is processing purchases on multiple devices, or processing purchases more rapidly than normal.
This value is unreadable except for on the payment you set it on, you aren't able to read SKPaymentTransaction.payment.applicationUsername it is always nil.
On a side note, I think this is a flaw in StoreKit as it makes it impossible to link transactions within an iTunes account to an application account, so if you have users sharing an iTunes account it is very difficult to not double grant purchased content.

In-App-Purchase Bug

I'm developing an iOS application in which users can buy an extra feature through an in app purchase. I have gotten the in app purchasing and restoring the purchases working correctly, where I save a boolean to the NSUserDefaults saying whether they have successfully bought and or restored the purchase.
However there is a bug where if User 1 buys the in app purchase on their phone, then logs into the App Store on User 2's phone and restores purchases. Then logs out again and allows User 2 to log back in, it means User 2 still has that in app purchase unlocked for free as the boolean in NSUserDefaults is still set to true.
I'm trying to find a way to query the user's App Store email or another way to check if the user's App Store email is the same as the one they bought the purchase under. But this needs to be done locally as the user could use this feature when they don't have any internet connection.
Does anyone have any suggestions of how I can do this or any better practices?
Thanks
This is, unfortunately by design, so as the old adage goes "it's not a bug, it's a feature".
It's designed like this so a user with multiple devices (e.g. iPhone and iPad), can make a purchase on the first device and benefit by restoring their purchases on their other device(s).
Of course this opens the door for people to share purchases across other peoples devices just as you describe.
The good news is there is a property on SKPayment called applicationUsername, its purpose as the docs state...
Use this property to help the store detect irregular activity. For example, in a game, it would be unusual for dozens of different iTunes Store accounts to make purchases on behalf of the same in-game character.
If you don't have anything unique to identify the user, e.g. an email, username, id, etc then your problem can't be solved I'm sorry, but if you do, keep reading, this is where it gets interesting.
When the user restores their purchase you should use the restoreCompletedTransactionsWithApplicationUsername method to make the call instead of restoreCompletedTransactions.
The docs are unclear what happens next, but my understanding is transaction state will be SKPaymentTransactionStateFailed if the username sent in the purchase call is not the same as the username sent in the restore call.
But if i'm wrong then you should be able to find the applicationUsername on the payment property of the transaction and compare it yourself before setting your property on NSUserDefaults. Sorry I haven't tried this myself, I only know the theory.
When setting the username, don't set it as plain text, the docs on applicationUsername suggest that...
The recommended implementation is to use a one-way hash of the user's account name to calculate the value for this property.
And further in Detecting Irregular Activity they explain...
Don't use the Apple ID for your developer account, the user's Apple ID, or the user's unhashed account name on your server.
Also, ideally you will be using a constant identifier, i.e. something that can't be changed for the user. If you use a hash of their email address for instance, if they change it and you didn't keep that hash as a constant restoring will fail as an unintended side effect.
Hope this helps.
You can control it via your servers by checking originalTransactionIdentifier of the receipts. As this remains same for all the purchased from one itunes account. You can associate this originalTransactionIdentifier with the username. Next time if you receive some already stored originalTransactionIdentifier against a different username, you can take the actions according to your suitability.

Can iphone users delete your app's keychain data?

To contextualize the question, our use case is an app through which users can get free promotional items on signup. We would like to prevent abuse of the system by limiting to one promotion per device. Since we can't access the UDID, we need some other (mostly) reliable way to check if the phone has already signed up an account. We don't need a solution that is impossible to circumvent, just one that is highly inconvenient to circumvent.
If we store a unique key in the user's keychain, then we'll be able to read it again even if they uninstall and reinstall the app. We're considering using this method to track devices that have already signed up accounts.
Questions:
Is there a relatively easy way that the user could delete or change our app's keychain data? You can assume that the user's device is not jailbroken, and that they will not go to the trouble of completely reinstalling the OS.
What are other options besides using the keychain we might consider? Keep in mind that the app communicates with our server during signup, so we can store previous signup information of any kind on the server.
Are there any gotchas or problems with the keychain method we should know about?

How can I limit ios app purchasers to one account?

I want to create a social networking ios app which costs money, and when the user buys it they are allowed to create only one account on the app. So the purchase is associated with the account somehow... or the app has a specific id on each device its bought on... would I need to use gamecenter somehow...
How would I approach something like this?
Any help would be greatly appreciated.
First of all, more than one person may use the device (think a family with an iPad) so what your doing is inherently a bad idea.
That said, you can use an in-app purchase to let them buy account access - check with the server first to make sure a desired username is available, then let them use an in-app purchase to buy access, and on your server tie that purchase ID to the user name. Even if they deleted the app and tried to re-purchase (while logged into the same iTunes store account) it would see a purchase had been made and essentially be the same user. It even does kind of work with multiple users as they could switch iTunes accounts (though that is a pain).
I would also suggest trying to tie it to the users iCloud account in some way, which is easier for users to switch between on an device (a family might use one iTunes account but have separate iCloud accounts).
If you don't want to give Apple 30% of the registration fee, your only option is to have users sign up on the web and pay there, then have a login they can use in your app. Then it's a matter of trying to prevent duplicate logins on you server, although as a last point of thought, I can't remember a business that succeeded by refusing people's money.
You could generate an unique id on the phone, store it in the keychain, and use that to communicate with the server.
Since you are creating your own account management system, just save the user information once they do the account creation bit and never show the option again. Because developers no longer have access to the device id, you may have problems identifying an existing user if they delete and re-install the app, but I am not sure that there is a solution to that problem.
Edit: As a commenter to this question mentioned, you can save things to the keychain. Check out this link for a good lib for modifying the keychain.

Saving information about previous purchased items in iOS

I am working on an app that has a number of in app store purchase items that will enable certain functionality.
After each item is purchased, I would need to remember it (of course). I would also like to persist this information in iCloud in case the app is deleted or is installed on another device.
What is the best data structure to be used?
Can user defaults database be used for this?
What is important is that the user will not have access to change those values and enable by themselves the paid functionality.
Can user defaults database be used for this?
It can, but it's not the best idea to do so, since
What is important is that the user will not have access to change those values and enable by themselves the paid functionality.
and NSUserDefaults stores its contents as binary or plaintext property lists. Easily changeable on a jailbroken device. (Also changeable on a non-jailbroken one by modifying the iTunes backup files).
What you could do is either store them in the keychain, although the keychain is not really designed for this (and it can also be dumped on a jailbroken device using Ptoomey3's awesome Kaychain-Dumper tool), or better store it remotely on your server and let your server check what the user has purchased.
Of course, if it's not only the server that does the check, so for example you don't only send or don't send content based on purchases, but you also use this check for performing actions within your app, then it also can be hacked (google "MobileSubstrate cheat DoodleJump" for a nice example).
All in all: there's no perfectly secure system.
I use the KeychainItemWrapper class to store a flag in the keychain. The nice thing is, so far, is that the keychain survives an app deletion. IF the user then reinstalls the same app on the same device, the keychain data is still there.
There is no need to use iCloud. If the user installs your app on another device, you just need to provide a "Restore Purchases" button in your app. Then you call the Store Kit APIs to restore existing purchases. The "In-App Purchasing Programming Guide" covers how to do this.
This same functionality will allow a user to restore their purchases even if the flags in the keychain are lost.

Resources