Using Fingerprint.js in a one-time device authorization workflow

Note: This was used in a project with an intentionally small deployment, so this solution is not terribly secure, or production-ready, but did get the job done for those involved.
I recently began developing a mobile app with Phonegap for a web app with a Rails backend that used Google OAuth to manage logins, and the app’s templates (in the Rails app itself) relied on a flag to determine if a user was approved or not before rendering anything.
This was a little trickier to do with Phonegap because I could not rely on server-side resources, so I extended the RESTful interface in use already (a simple API that generates a playlist, which is used by the player on my app to stream automatically-selected content continuously) to make this much more usable with a Javascript-based client.
This is a relatively simple thing to work around, but to make the experience more seamless, and because this was a media-centric app (streaming video), I wanted to offer mobile devices (and would reuse this workflow for authenticating users on a set-top box app, for example) the ability to authenticate one-time, and have subsequent logins be handled by a device identifier.
If this sounds familiar to anyone whose used a device like a Roku: Many of its channels do something similar; the device generates a code, you go to your web account, and enter the code, thus registering the device to your account. This is a lame, kinda amateurish, but effective means of doing something similar (the criteria you use for authentication, for example, is flexible, and ultimately up to you to make as easy to difficult to provide).
To facilitate this process, I used Fingerprint.js to generate device IDs that could be attached to my application’s users.
Here’s an overview of how the request workflow looks:
There are 3 endpoints in my API:
/fingerprint/:fingerprint
/sign/:fingerprint/:key
/validate/:fingerprint
A request to the first is made after grabbing the Fingerprint:
var fingerprint = new Fingerprint().get();
and then a GET request is made to the fingerprint endpoint; there are a few different responses, but basically, if a `new` response is given, the app prompts for an API key, and a request is sent to the /sign endpoint with the key and fingerprint in the URI (seen in format above). The sole purpose fo the /fingerprint endpoint is to determine if it is a new device or not; if it is not, the following step is skipped.
The /sign endpoint does two things with a valid request:
- Inserts the fingerprint along with the key into the device request table.
- Checks the key against the users table for an approved user matching that API key.
If no match is made, the process returns an error. If there is a match, the client-side script advances to attempting to reach the content playlist endpoint (/validate) where it supplies the fingerprint; if a match is found for a signed fingerprint, the playlist is returned and then loaded into the application’s player.
On subsequent visits, returning to the /fingerprint endpoint for a moment, instead of a “new” response, the response will be “validated”, and will simply advance to the /validate endpoint to load the content.
An apparent improvement to be made to this process, for example, if you wanted to prompt users for a key on every request, but still wanted to validate the device, the /sign endpoint could also handle the /validate endpoint’s work, and do all the checking in a single request.
The goal, however, of this 3 step process is to make this transparent to the user while still verifying they are a valid user.
A template for the client-side code is really as simple as:
var fingerprint = new Fingerprint().get();
var verification = httpGet(“http://api.hehe.biz/fingerprint/"+ fingerprint );
if (verification == “new”){…[jazz for inserting a new key and prompting authentication]…
}
if (verification == “validated”) {…[calling the /verify endpoint for content]…
}
and an `else` statement would suffice for error handling.