73

I am developing an iOS app which calls web-service for login and at that time i send login credentials to web server along with vendor identifier (identifierForVendor),to identify device uniquely for those credentials.So user can have only one device and one credential.

I got identifierForVendor with

NSString *uuid = [[UIDevice currentDevice] identifierForVendor].UUIDString

This identifier will then store in database of web server and also in device database.Next time when user opens application and will try to download data from web server firstly local identifierForVendor on users device will compare with identifier stored on web server.

Problem occurs when user uninstall app and reinstall it, I found that identifierForVendor is changed. So user cannot proceed further.

I read apple documentation UIDevice Documentation

As mention there, if all app from same vendor uninstalls from device then at time of new installation of any app from that vendor will take new identifierForVendor.

So how to deal with this in my case ?

Harshavardhan
  • 1,266
  • 2
  • 14
  • 25
  • 2
    I don't know if it's ok, but what about keeping it in Keychain? You check at launch if this identifier is in the KeyChain, and if not, your get one and store it in Keychain. – Larme Feb 19 '14 at 11:06
  • 2
    Hi Gekb, did you found any solution for your query. Even I am also facing the same case. – Kirti Nikam Jun 24 '15 at 07:40

8 Answers8

66

You may keep it in KeyChain

-(NSString *)getUniqueDeviceIdentifierAsString
{

 NSString *appName=[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];

 NSString *strApplicationUUID = [SSKeychain passwordForService:appName account:@"incoding"];
 if (strApplicationUUID == nil)
 {
    strApplicationUUID  = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
    [SSKeychain setPassword:strApplicationUUID forService:appName account:@"incoding"];
 }

 return strApplicationUUID;
}
nerowolfe
  • 4,787
  • 3
  • 20
  • 19
  • 11
    Will this work if iCloud KeyChain synchronisation is enabled? – Kristof Van Landschoot Jul 28 '14 at 14:01
  • Interesting question. Don't know – nerowolfe Jul 29 '14 at 06:04
  • 7
    This will not work when the KeyChain is synced, all synced device will get the same verdor id. – rckoenes Apr 10 '15 at 12:02
  • 8
    I'm just gonna vote this down because of the sync issue mentioned above. – Jonny Jun 17 '15 at 02:30
  • How about UIPasteBoard? – jayatubi Sep 14 '15 at 08:40
  • @KristofVanLandschoot to make it works with iCloud you need to use a keychain within iCloud: http://stackoverflow.com/questions/11489864/icloud-sync-keychain – loretoparisi Oct 13 '15 at 18:52
  • You can check my [answer](http://stackoverflow.com/a/34964617/5695358) regarding **SSKeychain** and synchronization mode – griga13 Oct 14 '16 at 10:19
  • iOS11+ new API called `DeviceCheck` can uniquely identify a device, check answer: https://stackoverflow.com/questions/24753537/unique-identification-of-ios-device-for-ios-7-0-and-above/45711623#45711623 – Muhammad Umair Aug 16 '17 at 10:57
  • Use updated version https://github.com/soffes/SAMKeychain of SSKeyChain. – Asif Raza Aug 31 '17 at 03:39
  • 1
    Watch out for keychain though, items in the keychain currently survive an app uninstall-install cycle, but that may change in the future. In iOS 10.3 beta 3, keychain items was removed but that changed back in the final version. See more at https://stackoverflow.com/questions/18911434/will-items-in-ios-keychain-survive-app-uninstall-and-reinstall . – Andreas Paulsson Oct 03 '17 at 12:17
  • @NSIceCode it seems that the token returned by DeviceCheck API is ephemeral (is not stable) see the docs: https://developer.apple.com/documentation/devicecheck/dcdevice/2902276-generatetoken – Alessio Dal Bianco Dec 05 '17 at 16:39
  • I'm using getting the identifier for vender as nil sometimes only in iPhone 8 and iPhone X, can anyone suggest why I'm getting the nil instead of UDID? @NSIceCode – S P Balu Kommuri Jan 04 '18 at 06:23
  • Keychain and UIPasteBoard are not persistent anymore : https://stackoverflow.com/a/48405739/62921 – ForceMagic Jan 23 '18 at 16:28
  • swift 4 compile time error: Use of unresolved identifier 'SSKeychain'; did you mean 'SecKeychain'? – Ammar Mujeeb Dec 16 '18 at 05:23
  • SSKeychain has been deprecated in favor of SAMKeychain (it is any third party library) – nerowolfe Dec 17 '18 at 07:47
19

Generally, don't use identifierForVendor. Instead, use NSUUID to generate a custom UUID and store that in the keychain (because the keychain isn't deleted if the app is deleted and reinstalled).

Wain
  • 118,658
  • 15
  • 128
  • 151
  • Is there a possibility for the identifierForVendor to return a duplicate in case for different versions of the same app on different devices? – PrasadW Jan 29 '15 at 23:07
  • I would expect yes, but an infinitesimally small chance – Wain Jan 30 '15 at 07:27
  • 1
    No chance of duplication. UUIDs are not random. They are calculated, and part of the UUID is the device ID. – Marcus Adams Mar 04 '15 at 16:06
  • 4
    "UUIDs created by NSUUID conform to RFC 4122 version 4 and are created with random bytes." - But none the less the chances of a repeat are very small, as someone else posted "Only after generating 1 billion UUIDs every second for the next 100 years, the probability of creating just one duplicate would be about 50%." - http://stackoverflow.com/a/1155027/859027 – mcfedr Dec 19 '15 at 07:29
  • It is good to use the identifierForVendor, but this should not be store in the keychain anymore. https://stackoverflow.com/a/48405739/62921 It's not persistent since iOS 10.3 – ForceMagic Jan 23 '18 at 16:27
  • @ForceMagic you were correct, but now you're incorrect :) – Matt Mc Feb 10 '20 at 20:57
  • @MattMc Haha! so they made it persistent once more? XD – ForceMagic Feb 11 '20 at 01:05
10

Addition to @nerowolfe's answer.

SSKeychain uses kSecAttrSynchronizableAny as a default synchronization mode. You probably don't want identifierForVendor to be synced across multiple devices so here is a code:

// save identifierForVendor in keychain without sync
NSError *error = nil;
SSKeychainQuery *query = [[SSKeychainQuery alloc] init];
query.service = @"your_service";
query.account = @"your_account";
query.password = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
query.synchronizationMode = SSKeychainQuerySynchronizationModeNo;
[query save:&error];
Community
  • 1
  • 1
griga13
  • 109
  • 1
  • 5
5

You can try use KeyChain to save your VendorIdentifier, that will exist till your device is reset, even if you uninstall your app.

iphonic
  • 12,615
  • 7
  • 60
  • 107
  • Unfortunately, they changed it recently, the KeyChain is not safe to store persistent data anymore. https://stackoverflow.com/a/48405739/62921 – ForceMagic Jan 23 '18 at 16:26
  • @ForceMagic Thanks for update, my answer is 3 years old, will find another way, al the answers to the question still stores in Keychain they are not updated, its pointless to downvote, as it used to work when the question was asked.. – iphonic Jan 23 '18 at 16:32
  • Yeah I do understand pretty much all the answers are old. I was seeing the downvote more like a "this is not true anymore" as opposed to "this is a bad answer". But maybe I'm wrong. – ForceMagic Jan 23 '18 at 16:34
  • @ForceMagic The answer still stands true see this https://stackoverflow.com/a/18944600/790842 and this https://stackoverflow.com/a/43063683/790842 – iphonic Jan 23 '18 at 16:43
  • From your link : `At Apple Documentation It is suggested that this is about to change and we should NOT rely on keychain access data being intact after an app uninstallation` – ForceMagic Jan 23 '18 at 17:07
  • @ForceMagic This means my answer still works, for SHOULD NOT rely, YES of course I personally never use Keychain, but as far as it is working and useful for solving some problem we can use it, even if it is not the best solution, I deserve upvote as the answer is still working. Thanks. – iphonic Jan 23 '18 at 17:57
4

Ok. I didn't want to use a third party - namely SSKeychain. So this is the code I tried, fairly simple and works well:

    NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];

KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:bundleId accessGroup:nil];
if(![keychainItem objectForKey:(__bridge id)(kSecValueData)]){
    NSString *idfa = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
    [keychainItem setObject:idfa forKey:(__bridge id)(kSecValueData)];
    NSLog(@"saving item %@", [keychainItem objectForKey:(__bridge id)(kSecValueData)]);
}else{
    NSLog(@"saved item is %@", [keychainItem objectForKey:(__bridge id)(kSecValueData)]);
}
Gautam Jain
  • 2,913
  • 30
  • 25
  • +1 This is a very simple solution, that combined with iCloud keychain sync (using the attribute kSecAttrSynchronizable), make the best solution at this question. – loretoparisi Oct 13 '15 at 18:55
  • 1
    @loretoparisi if you use kSecAttrSynchronizable wouldn't you have the same value on all your devices? – jcesarmobile Nov 13 '15 at 12:22
  • @jcesarmobile exactly, if you add ```kSecAttrSynchronizable``` to @gautam-jain code, you will get it on iCloud app properties and the synchronized to all your devices. – loretoparisi Nov 13 '15 at 12:30
  • then it's not a good idea if you want each device to have a different value – jcesarmobile Nov 13 '15 at 12:31
  • @jcesarmobile yes depending of your needs, you can preserve the device's identifier, the device's vendor of both like properties ```identifierForVendor ``` and ```advertisingIdentifier``` from ```ASIdentifierManager```. – loretoparisi Nov 13 '15 at 14:04
3

Swift version

func UUID() -> String {

    let bundleName = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
    let accountName = "incoding"

    var applicationUUID = SAMKeychain.passwordForService(bundleName, account: accountName)

    if applicationUUID == nil {

        applicationUUID = UIDevice.currentDevice().identifierForVendor!.UUIDString

        // Save applicationUUID in keychain without synchronization
        let query = SAMKeychainQuery()
        query.service = bundleName
        query.account = accountName
        query.password = applicationUUID
        query.synchronizationMode = SAMKeychainQuerySynchronizationMode.No

        do {
            try query.save()
        } catch let error as NSError {
            print("SAMKeychainQuery Exception: \(error)")
        }
    }

    return applicationUUID
}
Michael Kalinin
  • 127
  • 2
  • 6
2

There is no definite way to link a unique number to a device any more, this is not allowed with the Apple privacy guidelines.

You can try to save your own Unique ID in the keychain, but if the user clear his device this ID is also gone.

Generally is it just wrong to link a device to a user, since you are not longer identifying users but devices. So you should just change your API so that the user can re-login and that the vendor ID is bound to the users account.

Also what happens when the user has more then one device, like an iPhone and iPad, and uses you app on both? Since you authentication is based an unique ID this can not be done.

rckoenes
  • 69,092
  • 8
  • 134
  • 166
  • @DouglasHeld I understand that, but Apple made that impossible and I tried to explain why you should not. – rckoenes Apr 10 '15 at 09:57
  • @rckoenes You need to identify a device if you require hardware binding. How else would you stop a user from installing it on multiple devices? One guy pays once and then all his friends get it for free as well using his login credentials!? Doesn't seem reasonable to the developers. – Ash Apr 16 '15 at 02:43
  • 2
    @ash I hear you, but Apple is not allowing this. Per Apple Account 5 devices are supported. Restricting this is against Apple policy. – rckoenes Apr 16 '15 at 06:59
1

I had used KeychainAccess pod for this problem.

In your pod file :

pod 'KeychainAccess', '~> 2.4' //If you are using Swift 2.3 
pod 'KeychainAccess' //Defaults to 3.0.1 which is in Swift 3

Import KeychainAccess module in file where you want to set UUID in keychain

import KeychainAccess

Use below code to set and get UUID from keychain :

Note : BundleId is key and UUID is value

var bundleID = NSBundle.mainBundle().bundleIdentifier
    var uuidValue = UIDevice.currentDevice().identifierForVendor!.UUIDString

 //MARK: - setVenderId and getVenderId
    func setVenderId() {

        let keychain = Keychain(service: bundleID!)

        do {
            try keychain.set(venderId as String, key: bundleID!)
            print("venderId set : key \(bundleID) and value: \(venderId)")
        }
        catch let error {
            print("Could not save data in Keychain : \(error)")
        }
    }

    func getVenderId() -> String {
        let keychain = Keychain(service: bundleID!)
        let token : String = try! keychain.get(bundleID!)!
        return token
    }
Jayprakash Dubey
  • 35,723
  • 18
  • 170
  • 177