40

I'm developing chrome extension. I need the ability to identify each client as a unique client.

I can't store guid in a cookie since cookie can be deleted. I need something to be read from the system itself which is unique.

Now - I know that JS doesn't has access to client resources ( local resources) but - and here is my question :

Question

Does chrome extensions Js's provide API for getting unique client information ( I dont care what data - as long as it is unique).

Edit :

Just to clarify :

The user will be shown a unique key ( which is a hash data of his computer). this code will be sent to me , and I will provide matching result which the user will be sent (via email) and only then - he will be able to use the extension.

(no , not all countries support extension payment via wallet , im at one of those countries)

Royi Namir
  • 131,490
  • 121
  • 408
  • 714
  • i dont think that it is possible to read any hardware id or something via javascript, you could use this script, but i think it uses cookies http://www.dannytalk.com/read-google-analytics-cookie-script/. i found it via this post http://stackoverflow.com/a/12162449/3403216 . lg – linluk May 23 '14 at 19:13
  • @linluk this will provide info about how many users installed it. I need unique IDentifier to provide specific LiceneKey – Royi Namir May 24 '14 at 07:03
  • I'm probably missing something here, but why can't you use the client's IP-address (via Ajax-call to php script and back to your javascript)? – myfunkyside May 29 '14 at 19:40
  • @myfunkyside ip is not unique behind router – Royi Namir May 29 '14 at 19:58

3 Answers3

70

To uniquely identify a user, I would suggest to generate a random token and store it in your extension's storage (chrome.storage). The userid has to be generated only once, when the token does not exist in storage.

For example:

function getRandomToken() {
    // E.g. 8 * 32 = 256 bits token
    var randomPool = new Uint8Array(32);
    crypto.getRandomValues(randomPool);
    var hex = '';
    for (var i = 0; i < randomPool.length; ++i) {
        hex += randomPool[i].toString(16);
    }
    // E.g. db18458e2782b2b77e36769c569e263a53885a9944dd0a861e5064eac16f1a
    return hex;
}

chrome.storage.sync.get('userid', function(items) {
    var userid = items.userid;
    if (userid) {
        useToken(userid);
    } else {
        userid = getRandomToken();
        chrome.storage.sync.set({userid: userid}, function() {
            useToken(userid);
        });
    }
    function useToken(userid) {
        // TODO: Use user id for authentication or whatever you want.
    }
});

This mechanism relies on chrome.storage.sync, which is quite reliable. This stored ID will only be lost in the following scenarios:

  • The user re-installs the extension. Local storage will be cleared when uninstalling the extension.
  • One of the storage quotas has been exceeded (read the documentation).
    This is not going to happen because the only write operation occurs at the first run of your extension.
  • Chrome's storage gets corrupted and fails to save the data.
    Even if the user does not have Chrome Sync enabled, data will still be saved locally. There have been bugs with Chrome's internals that resulted in data loss, but these are incidents.
  • The user has opened the developer tools for your extension page and ran chrome.storage.sync.clear() or something similar.
    You cannot protect against users who possess the knowledge to mess with the internals of Chrome extensions.

The previous method is sufficient if you want to uniquely identify a user. If you really want to get a hardware-based ID, use chrome.storage.cpu and chrome.storage.memory as well. I don't see any benefits in using these additional sources though, because they can change if the user replaces hardware, and they are not unique either (two identical laptops would report the same values, for instance).

Arthur
  • 142
  • 5
  • 12
Rob W
  • 315,396
  • 71
  • 752
  • 644
  • What happens with this method if the UID is generated and then the user changes the signed-in Google account, without restarting the browser? Normally sync is the source of truth, but suppose that on the other account this extension has not been initialized. Local state has UID set, cloud has no record, I'd expect it to happily sync to the second account. – Xan May 25 '14 at 10:04
  • @Xan The data would indeed be synced to two Google accounts. That's how sync **within the same profile** works. If a user wants to sign in with a different account, then they have to create a new profile at the Settings page and log in to sync in that new profile. Sync works like this: The data is stored locally, but if you log in to another device with a Google account, the data is synchronized with the other device as well. If you want to allow only one account on the same computer, use `chrome.storage.local` instead of `.sync`. That could make some users angry though. – Rob W May 25 '14 at 10:32
  • The OP asked for identifying a "client", which in itself is ambiguous, but then he talks about, basically, a hardware fingerprint. So he probably wants `local`. Also, while a user can wipe the storage, he can also clone it, pretty easily. – Xan May 25 '14 at 10:36
  • Rob , there's a problem . If `chrome.storage` is using the localstorage and I save a value and the user email me the generated unique value and I email him back the corresponding matching key to use the software , and then he cleans his entire catch ( ccleaner.exe for example) - his key which I supplied to him - will not work anymore. because the old matching guid was deleted. he will have to regenerate a new key and email me back again.....no ? – Royi Namir May 25 '14 at 10:54
  • @RoyiNamir Cache != Cookies != extension storage. I fully expect CCleaner to be able to delete the cache, and perhaps also cookies. But I'd be surprised if it deleted the extension's storage as well. Even if it manages to delete the storage, then the settings should sync back. If it doesn't then you should try to fingerprint the user in other ways. Take a look at http://stackoverflow.com/questions/15966812/user-recognition-without-cookies-or-local-storage for inspiration. – Rob W May 25 '14 at 10:58
  • Just curious - (I meant cache as trackable items. what about this http://i.stack.imgur.com/rboJU.jpg ? ) (chrome delete) – Royi Namir May 25 '14 at 10:59
  • @RoyiNamir The Clear browsing data method of Chrome does not clear any extension data that is stored in `chrome.storage` (not even other data stored in the `document.cookie` or `localStorage` of extensions). – Rob W May 25 '14 at 11:03
  • 1
    @FlashThunder That remark was explicitly mentioned in my answer. This is the closest that you can get in terms of getting an unique identifier. It is even more reliable than cookies (which are often used on websites). – Rob W Jan 28 '15 at 14:46
  • 3
    "This stored ID will only be lost in the following rare scenarios" You forgot one scenario: Simply reinstalling the app. – Farzher Jul 07 '15 at 17:20
  • Great Idea. I have created an extension using your answer. https://github.com/Antony007/ADNIdentifier – Antony Jan 21 '18 at 07:25
6

As Xan suggested, the chrome.identity API is probably your best choice. You can get the users e-mail address and use that as a random seed to generate a code of your choosing. The user info also includes an "id" field which I believe is unique but I haven't ever seen any documentation that substantiates that. You can then use the chrome.storage.sync API to store the generated key in the users online data storage for your app. This way the user will be able to access their private key whenever and where ever they log in on any device.

Please note that you will have to enable the oAuth2 api's in the developers console for your application and include the application key and proper scopes in your app manifest.

Here is a crude example:

function getUserInfo (interactive, callback )
{
    var xmlhttp = new XMLHttpRequest();
    var retry = true;
    var access_token;

    getToken();

    /**
     * Request the Auth Token
     */
    function getToken()
    {       
        chrome.identity.getAuthToken( { 'interactive': interactive }, function (token) {
            if ( chrome.runtime.lastError )
            {
                console.log( "ERROR! " + chrome.runtime.lastError.message );
                return;
            }
            if ( typeof token != 'undefined ')
            {
                access_token = token;
                sendRequest( );
            }
            else
                callback( );
        });

    }

    function sendRequest()
    {       
        xmlhttp.open('GET', 'https://www.googleapis.com/userinfo/v2/me' );
        xmlhttp.setRequestHeader('Authorization','Bearer ' + access_token );
        xmlhttp.onload = requestComplete;
        xmlhttp.send();
    }

    function requestComplete()
    {
        if ( this.status == 401 && retry )
        {
            retry = false; // only retry once
            console.log( "Request failed, retrying... " + this.response );
        }
        else
        {
            console.log( "Request completed. User Info: " + this.response );
            callback(null, this.status, this.response );
            var userInfo = JSON.parse( this.response );
            storeUniqueKey( userInfo );

        }
    }
}

function storeUniqueKey( info )
{
    var key;
    // TODO: Generate some key using the user info: info.loginName
    // user info here contains several fields you might find useful.
    // There is a user "id" field here which is numeric and I believe that
    // is a unique identifier that could come in handy rather than generating your 
    // own key.

    ...

    chrome.storage.sync.set ( { user_key: key } );
}
Zoccadoum
  • 792
  • 1
  • 9
  • 16
3

To add to Rob W's answer. In his method, the saved string would propagate to every Chrome instance signed in with the same Google user account - with a lot of big and small if's.

If you need to uniquely identify a local user profile, and not all Chrome profiles with the same Google user, you want to employ chrome.storage.local in the same manner. This will NOT be a unique Chrome install identifier though - only a profile within that install.


What also needs to be noted is that all this data is not in any way or form tied to anything - it just has a good probability of being unique. But absolutely nothing stops user from reading and cloning this data as he sees fit. You cannot, in this scenario, secure the client side.


I'm thinking that a more secure way would be to use chrome.identity API to request and maintain an offline (therefore, not expiring) token as proof of license. The user cannot easily clone this token storage.

I'm not versed in OAuth yet, so if anyone can point out what's wrong with this idea - they are welcome to.

Community
  • 1
  • 1
Xan
  • 66,873
  • 13
  • 150
  • 174