James Montemagno
James Montemagno

Live, Love, Bike, and Code.

Tags


Twitter


Unique Device & App Installation Identifiers for Mobile Apps

James MontemagnoJames Montemagno

One common query I get all the time from developers is how to get a unique device or application identifier for their mobile app. After deciding to not include an API for this in Xamarin.Essentials we heard a lot of different use cases for it including:

  • Client/Server Communication
  • Login Validation
  • In-App Purchase Verification
  • Licensing Purposes

The next obvious question that you may have is why didn't we include it in Xamarin.Essentials if everyone seemed to want it? The answer is actually pretty straight forward: There is truly no consistent API across every platform to deliver an identifier that is unique to the device or application. On top of that developers all wanted to use the API a bit differently and in some cases make it a core part of processing transactions and that is something we don't want to get in the middle of.

So what can you do if you need this identifier? I would first stop and ask yourself if you can use some sort of User Identifier based on login information as this will ensure uniqueness that you control. If not then there are a few options to go down then trying to implement this.

Platform APIs Available

Each platform offers up something a bit different:

iOS: IdentifierForDevice

This API is pretty interesting as it will give you an alphanumeric string that uniquely identifies a device to the app's vendor. Based on your Bundle ID that you set Apple will return back the same identifier. The issue that I always found is that this can be spoofed by someone else setting a similar Bundle ID, but it is in general a decent API:

public string Id => UIDevice.CurrentDevice.IdentifierForVendor.AsString();

Android: Serial, getSerial & AndroidId

What a mess Android is as there are now three APIs that do similar things and even Google's documentation is all over the place on which to use!

A recommended route would be to attempt to grab the Serial number when available. In the world of Xamarin we will automatically figure out whether to use the static property or the new getSerial() method introduced in Android 9.0 (which requires the READ_PHONE_STATE). The issue here is that this can be modified via rooted devices and sometimes is not available on all hardware. This means that you need to attempt to figure out something else to use, which would be the ANDROID_ID. This is a unique number that is randomly generated when the device is setup, unless you are on API 26+ which is a number that is unique for each combination of app-signing key, user, and device.... As you can see now we have like 4 different identifiers here that all mean different things. If you went this route then your code would look like this:

string id = string.Empty;
public string Id
{
    get
    {
        if (!string.IsNullOrWhiteSpace(id))
            return id;

        id = Android.OS.Build.Serial;
        if(string.IsNullOrWhiteSpace(id) || id == Build.Unknown || id == "0")
        {
            try
            {
                var context = Android.App.Application.Context;
                id = Secure.GetString(context.ContentResolver, Secure.AndroidId);
            }
            catch(Exception ex)
            {
                Android.Util.Log.Warn("DeviceInfo", "Unable to get id: " + ex.ToString());
            }
        }

        return id;
    }
}

Before going down this route think about the implications here of your code and what you are trying to accomplish.

UWP: GetPackageSpecificToken or GetSystemIdForPublisher

We are in a tricky place for UWP as well depending on what we are trying to do. GetSystemIdForPublisher is very similar to what iOS does, but it may not be available in all versions of Windows. The next level is trying to grab a unique hardware identifier for the specific app with GetPackageSpecificToken. So in this case your code may look like:

string id = null;
public string Id
{
    get
    {

        if (id != null)
            return id;

        try
        {
            if (ApiInformation.IsTypePresent("Windows.System.Profile.SystemIdentification"))
            {
                var systemId = SystemIdentification.GetSystemIdForPublisher();

                // Make sure this device can generate the IDs
                if (systemId.Source != SystemIdentificationSource.None)
                {
                    // The Id property has a buffer with the unique ID
                    var hardwareId = systemId.Id;
                    var dataReader = Windows.Storage.Streams.DataReader.FromBuffer(hardwareId);

                    var bytes = new byte[hardwareId.Length];
                    dataReader.ReadBytes(bytes);

                    id = Convert.ToBase64String(bytes);
                }
            }
            
            if (id == null && ApiInformation.IsTypePresent("Windows.System.Profile.HardwareIdentification"))
            {
                var token = HardwareIdentification.GetPackageSpecificToken(null);
                var hardwareId = token.Id;
                var dataReader = Windows.Storage.Streams.DataReader.FromBuffer(hardwareId);

                var bytes = new byte[hardwareId.Length];
                dataReader.ReadBytes(bytes);

                id = Convert.ToBase64String(bytes);
            }
            
            if(id == null)
            {
                id = "unsupported";
            }

        }
        catch (Exception)
        {
            id = "unsupported";
        }

        return id;
    }
}

Just like Android you may get different results based on what you are looking for.

A True Cross-Platform Identifier

Now that we have seen each of the platforms and how inconsistent they are, let's talk about a better way of doing this. What most developers are attempting to do is ensure that when an application is installed they can get some unique identifier for that installation. If the app is transfered to a new phone then the identifier is the same, but if it is un-installed then a new identifier is generated. This can easily be accomplished by using Xamarin.Essentials Preferences API to save a Guid when your app starts if it doesn't exist yet. The code is super simple:

var id = Preferences.Get("my_id", string.Empty);
if(string.IsNullOrWhitespace(id))
{
   id = System.Guid.NewGuid().ToString();
   Preferences.Set("my_id", id);
}

This will ensure a unique identifier for the installed application and it is actually the same mechanism that App Center uses for their SDK across each platform! You should of course be aware that this Guid will sync across the user in some instances based on their device and account setup, which is probably what you want to have happen.

There you have it a full break down of each platform with the complexity that each offer. However, my recommendation remains that you should just generate a Guid and go from there :)


Tags



Live, Love, Bike, and Code

Checkout my monthly newsletter that you should subscribe to!

Comments