APNS token collision, stored in Postgres - ios

I use push notifications and store device tokens like I assume everyone else does. First I transform them into a string my app:
NSString *deviceTokenString = [[[token description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]]
stringByReplacingOccurrencesOfString:#" " withString:#""];
Then I PUT them to my server, where ActiveRecord stores them in a character varying(255) column:
Device.where(:token => device_token, :username => username).first_or_create!(:model => model)
I have a validation that ensures no two tokens are the same, which I understand should always be the case:
class Device < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :token
end
However, I've started to see validation errors for token uniqueness:
ActiveRecord::RecordInvalid: Validation failed: Token has already been taken
Manual query in psql confirms that a device is trying to register with a token already in the table under a different user. Isn't this supposed to be impossible? Is something in the way I'm transforming tokens truncating them? I checked every code example I could find when the problem first occurred and everyone seems to use the method I've listed in the first code sample.

It can happen that a device tries to register with a token already in the table under a different user if someone logs out and then logs in with a different account.
I would do the following on the server for a user user and a token string token (assuming that only one user can be logged in on one device at a time):
Check if there is a Device for token_string.
If there is no device, create one for token_string and user.
If there is a device and its user is not user, update its user to user.
That way, the push notifications will be sent for the last user that logged in on the device.
Concerning your way of transforming the NSData to a hex string on the device, you should not rely on -[NSData description]. Better do it programmatically (typed in, not tested):
- (NSString *)hexStringForData:(NSData *)data
{
NSUInteger length = data.length;
const char *bytes = data.bytes;
NSMutableString *result = [NSMutableString stringWithCapacity:length * 2];
for (int i = 0; i < length; i++) {
[result appendFormat:#"%02x", bytes[i] & 0xff];
}
return [result copy];
}

I'll wager a guess at this one, but take it for what it's for, a guess.
When iOS devices are restored from backups, or when they are "restored" onto new devices, say, someone upgrading from a iPhone 4 to iPhone 5, or when someone gives their iPhone to their wife or sells it on eBay, you will get duplicated/redundant/confusing device data. I've definitely seen that happen, but not specifically with APNS tokens.
Here is what the APNS docs have to say about it:
By requesting the device token and passing it to the provider every
time your application launches, you help to ensure that the provider
has the current token for the device. If a user restores a backup to a
device or computer other than the one that the backup was created for
(for example, the user migrates data to a new device or computer), he
or she must launch the application at least once for it to receive
notifications again. If the user restores backup data to a new device
or computer, or reinstalls the operating system, the device token
changes. Moreover, never cache a device token and give that to your
provider; always get the token from the system whenever you need it.
If your application has previously registered, calling
registerForRemoteNotificationTypes: results in the operating system
passing the device token to the delegate immediately without incurring
additional overhead.
So, I'm not looking at your code, but it seems likely that your "duplicate" tokens have to do with some combination of not registering every time, some kind of caching, and device restoration.

It is important to understand what happens when a device registers. It sends your server a POST on the following:
/passkit/v1/devices/<deviceID>/registrations/<typeID>/<serial#>
And within the JSON payload is the push_token.
What matters is both the deviceID and push_token. As far as Apple is concerned, you communicate with the device through Apple's system with the push_token and nothing else.
As for the deviceID, it is the actual physical device that is used. The fact that the device might want to register multiple times is irrelevant, and your code should simply update the push_token in your DB based on the latest registration attempt. That's about it.

Related

Voip Push: Under what circumstances does didInvalidatePushTokenForType get called?

The documentation for didInvalidatePushTokenForType says its optional to implement and also says this
This method is invoked if a previously provided push token is no
longer valid for use. No action is necessary to request registration.
This feedback can be used to update an app's server to no longer send
push notifications of the specified type to this device.
Why on earth therefore would somebody not want to implement this? If the token is no longer valid then a server will never be able to send Voip pushes to that device again, so doesn't the app on the handset want to know as soon as possible if its invalided so it can send a new token to the server?
I've been trying to search for info and use of didInvalidatePushTokenForType() but it seems everybody just copy and pastes this method into their source code because everybody else has copy and pasted it. But nobody seems to ever do anything with it.
But seems to like it should be a vitally important method to make use of, so why does nobody apparently?
When would the token become invalid?
Thanks for the asking great question.
1) When would the token become invalid?
If we are update app from the App Store then APNS token doesn't change.
reinstalling the OS or Update OS or Reset iOS device then APNS token does change(Upgrade or downgrade OS).
Device token Invalidated or expired after 2 Years.
iOS9 and later device token changes if I reinstall an app.(As per my experience and Knowledge).
Download app from App Store then run your code using X-Code in this case device token will change.
2) Important of didInvalidatePushTokenForType() or Why? didInvalidatePushTokenForType() is optional
Let's clarify about didInvalidatePushTokenForType() method.
Once token got changed then called didInvalidatePushTokenForType() and didUpdatePushCredentials() method, So all code is placed in didUpdatePushCredentials() instead of didInvalidatePushTokenForType().
That's why Developer doesn't give important to didUpdatePushCredentials() method.
Find Reference from Here

Strange Thing Going on with UUID on messages extension

I am creating this messages extension that is a game.
When I receive a conversation on didBecomeActiveWithConversation I grab my UUID and the opponent's UUID, for example:
myUUID = [conversation.localParticipantIdentifier UUIDString];
opponentUUID = [[conversation.remoteParticipantIdentifiers firstObject] UUIDString];
at this point if I print this I get something like
myUUID = 3A00236E-606E-41BE-BD11-97658AF13434
opponentUUID = 794DC7EB-E0AF-46CD-9BF0-5B6D39CC6773
Then I make my move in the game and send to the opponent.
On the simulator I switch from "Kate" to "John Appleseed".
When the method didBecomeActiveWithConversation triggers again, now for the other user, I grab both UUID again. This is the result:
myUUID = 3A00236E-606E-41BE-BD11-97658AF13434
opponentUUID = B4621E05-4407-443E-9526-C8F0C82753D6
What? myUUID is the same as before and my opponentUUID is completely different?? By switching users on message I was expecting to see the entries reverted. How can that be? Bug?
Apple doesn't like issuing numbers that can be used to identify users beyond what is strictly necessary. In this case, the localParticipantIdentifier property is unique to each device (so person A has different identifiers on each device they are talking with) and each app install (so two different apps will see two different identifiers).
In fact, if the user deletes and reinstalls your extension, the identifier will be changed – just like identifierForVendor on UIDevice.
From the docs:
This UUID is scoped to this device. It remains stable as long as the extension is enabled. If the extension is disabled and reenabled, or if the containing app is removed and reinstalled, the UUID for the local participant changes.
This particular case is complicated by the fact that you're using the simulator, which is rigged by Apple to look like two accounts even though it's one device. I suspect that when you run the same code on two real devices you'll find two completely different numbers on both sides.
It's worth adding that there are several open radars for Messages identifiers, not least this one, so you might be right that it's a bug.

Uniquely identify the device in iOS

I am developing a corporate application on the iPad for a certain business requirement.
This app is meant to use in a specific number of devices which is predefined by the admin.
But I also need the application to reject any login requests even if it is from an authorised user,when he or she is using a device which is not defined by the admin.
Edits:
Say I have 2 devices and I have my credentials to login to the app, And my need is, to restrict the login from the devices which is not mine.
For that I have to identify whether the login request is comes from my device or not.
Previously we could use device UDID to do this, but now it is deprecated.
Can any one please suggest a method to implement this ?
try this. for more info check UIDevice
// IOS 6+
NSString *uniqueIdentifier = [[NSString alloc] initWithString:[[[UIDevice currentDevice] identifierForVendor] UUIDString]];
You can use iCloud over here because UUID has been deprecated and vendorId is uniqe but might be change if you uninstall the app and install it agian,
So I would suggest iCloud will be safer, what you cna do is at the time of application launch you can generate one token which is unique and save it to your iCloud data storage along with user credentials,
So from the next time onwards when user will try to login you can check it with iCloud.
How about using Apple's enterprise distribution system? That will allow you to deploy the app to a corporation, and have tight access control.
https://developer.apple.com/programs/ios/enterprise/
Just Trick:
You can Implement APNS code to your Project and Get Device Token.
The Device Token is Unique One. But The user Must Allow the APNS.
Note: APNS Device Token is changed to Following reasons.
Change of Bundle id.
Change of Development Mode(Sandbox/Production)
I have implemented a solution for exactly your problem, The best solution (and Apples recommended route) is to create a UUID unique to your App like this:
NSString *uuidString = nil;
CFUUIDRef uuid = CFUUIDCreate(NULL);
if (uuid) {
uuidString = (NSString *)CFBridgingRelease(CFUUIDCreateString(NULL, uuid));
CFRelease(uuid);
}
Then, and this is the key, you can store that to the iOS keychain (Handy classes here: https://github.com/lukef/IXKeychain) and values in the iOS keychain are NOT removed when the user uninstalls the App, so you can persist your own UUID through App installs which is a key part of managing a specific number of devices against a user account.
This method will return a string for every device. Since it is gonna change every time for single device
so we are storing it in a keychain and can refer it whenever we need it.
+ (NSString *) uniqueDeviceIdentifier
{
NSString *deviceUUID = [[SGKeyChain defaultKeyChain] stringForKey:#"uniqueId"];
if (!deviceUUID) {
if (!deviceUUID.length) {
NSString *deviceUUID = #"";
CFUUIDRef uuidRef = CFUUIDCreate(NULL);
CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
CFRelease(uuidRef);
deviceUUID = [NSString stringWithFormat:#"%#",[NSString stringWithString:(__bridge_transfer NSString *)uuidStringRef]];
[[SGKeyChain defaultKeyChain] setObject:deviceUUID forKey:#"uniqueId" accessibleAttribute:kSecAttrAccessibleAlways];
}
}
return deviceUUID;
}
yo can refer to this repository... https://github.com/sgup77/SGKeyChainWrapper for SGKeyChain implementation
Ok I will share the approach that we follow for a B2B enterprise app.
.
Every User has login Id and Password.
1. So user register his device with Server using deviceReg API which takes clientDeviceId as param(client generated uuid) along with username and pass.
2. Server returns a server generated unique identifier to be used by application on that particular device.
Conclude - in this way you can restrict the user with a certain device.
You can use below method to generate deviceSpecific client UUID
- (NSString *)getUuid
{
CFUUIDRef uuidRef = CFUUIDCreate(NULL);
CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
CFRelease(uuidRef);
NSString *uuid = [NSString stringWithString:(__bridge NSString *)uuidStringRef];
CFRelease(uuidStringRef);
return uuid;
}
Please note:
I remove the explanation about using AuthKey, AccessToken and others which we use for security purpose as you do not use any auth server.
I hope it helps.
Update 1.
Since you are having an enterprise application so i am sure you would be having atleast the user e-mail ids.
So the account manager should than 1st send a email with one time token to all active accounts.
This token can be requested by the application while registering the device and send to server for validation.
Also the server invalidates the token once used to avoid misuge.
There should be a migrAtion api which uses the server generated device id and a migration token if user migrates the device.

Can device token of Push Notification be used as a unique identifier?

Problem: using UDID is deprecated - we cannot use it anymore.
There are some solutions on the net: generate GUID and store it in the "safe place", iCloud, IdentifierForVendor starting with iOS6, OpenUID, SecuredID and so on...
Request:
I need to have a unique identifier of the Device to store user data on our server.
Question:
Can I use deviceToken of Push Notification as a unique identifier?
What are the pros and cons of this idea?
(-) user can disable push notifications
(+) unique number
(+) supported in all iOS
This is a terrible idea, the token can change if the user changes device or for some other unknown reason.
The user can have multiple devices
If the user reinstalls the app they can get an other token
It's not 100% that the user will keep the same token.
And most important of all: You are identifying devices not users!
One solution is to generate a UUID and save it in the user keychain where you retrieve it. But this can also be removed if the user clear the device.
You best option is to allow the user to login with an account, which that can create. Then you can combine this with the UUID in the keychain.
You should use identifierForVendor. The deviceToken for push notification is unique, but CAN change.
The token can change if the user reset the device, for unique device identifying you can use the following code
float currentVersion = 6.0;
NSString *udid = nil;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= currentVersion)
{
//below code is taken from the Apple Sample code, to check you can download the files
https://developer.apple.com/library/ios/releasenotes/StoreKit/IAP_ReceiptValidation
// OR
http://developer.apple.com/library/ios/releasenotes/StoreKit/IAP_ReceiptValidation/VerificationController.zip (line number 319)
udid = [UIDevice currentDevice].identifierForVendor.UUIDString;
}
else
{
//may cause apple rejection
udid = [UIDevice currentDevice].uniqueIdentifier;
//i think we can use the below link for ios5 or below but not sure it may accept or reject
https://github.com/gekitz/UIDevice-with-UniqueIdentifier-for-iOS-5
}
//just saw a link which may help you better and have a look at image
http://www.doubleencore.com/2013/04/unique-identifiers/
can someone suggest the best way to persist the unique id even after reinstall app, delete app or system restart or system boot or factory reset

How does an iOS device identify itself to server without user log in

I am writing an iOS app with a Rails API backend. The Rails backend will serve JSON data to the app. I have the following requirements.
The app will be a free download
The app will show data on a map
The app will show data in the vicinity of the user
Upon loading the app the device should send some unique identifier to the server identifying itself as a device that is running this app.
There will be no authentication for the user as it is not required. The data is available to anyone who downloads the app. All the server needs to know is that the client is a device running the app. The server cannot serve data to any other client
I would like to run the data using SSL between the device and server
The user location will be sent to the server and the server returns the corresponding pieces of data that are in the vicinity of the user
The client receives the JSON and caches the data locally.
Question: Given these requirements, how to set up steps 4 & 5?
Also: If I want to search more on this topic what keywords should I be googling for?
Consider using OpenUDID or SecureUDID.
I give you 2 options.
First of all, the easy way. From some time, apple forbids access to the device ID. However, they give you a device token instead.
To get this unique token, the user must register for remote notification.
Upon application launching, call the following function:
[[UIApplication sharedApplication] registerForRemoteNotificationTypes (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
Then this callback will be called:
- (void)application:didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken.
Send the token to your server and you're done. Problems with this approach are obvious. Your user will have to register for remote notification.
Another approach is to use the MAC address of the wi-fi board.
To do this:
IPAddress.h
IPAddress.c
Import this files into your project.
Then use this function:
InitAddresses();
GetHWAddresses();
for (int i=0; i<MAXADDRS; ++i)
{
//There is a way you can obtain more info about the hw_addrs, but in general, it's the first.
NSLog(#"MAC: %s", hw_addrs[i]);
}
FreeAddresses();
Create a hash using the mac address above and you're done.
Hope it helps.
Upon first launch, the app sends a request to the server saying Hi, I'm a new client, give me an id! The server generates a new, random id and sends it back. The app saves the id locally and uses it henceforth to uniquely identify itself.

Resources