Stop working with “User-Agent” header (Browser Fingerprinting)

How would you identify each individual user by its browser? Would you rely on “User-Agent” header that is sent alongside the HTTP requests? Just like any other information that is part of client-side request made to your server, the “User-Agent” header can be easily spoofed/manipulated and is, therefore never really reliable. Not only this but essentially any information received in any HTTP request pertaining to user’s end (OS/device/browser) can simply NOT be relied upon.

Let us put this topic in perspective now: You want to protect your users and therefore one of the key security features that you may offer to your users is to detect and prevent possible unauthorized logins to your user’s accounts when a change in device or browser or operating system is detected. An unreliable but easy way to achieve this is to simply accept the “User-Agent” string, parse and process it. You may either hash it or store the string as-is and compare it to the user’s last authenticated device or you may go a step further and attempt to process this which in itself is a miserable and cumbersome process.

Not only that “User-Agent” string is unreliable, should not be trusted but parsing it accurately is a nightmare for developers and simply cannot be achieved without the help from 3rd party libraries that were built specifically for the purpose. Its a real shit-show! Chrome is pretending to be Safari while Microsoft Edge is pretending to be both Chrome and Safari, Windows 11 continues to describe it self as Windows 10 and so on… There is in-fact a long history to this ridiculousness which dates back to Browser Wars and is on-going to date and to the point where its debated depreciate or to even drop “User-Agent” header entirely, and this is all happening as we speak and a new concept called “UA-CH” (User-Agent Client Hints) which has been a work in progress but already supported by many modern browsers. It is a widely acknowledged opinion, which is true to some extent that classic “User-Agent” header strings tend to share excessive information about their users, and so much so that in some cases combined with other information gathered with in the browser, it may even be considered invasion of privacy.

Now, lets come back to our scenario where we are trying to identify the browsers which our users have used to authenticate their sessions and then we intend to detect any changes to device/browser next time a user attempts to login. This security feature is in fact a necessity and does not involve any breach of privacy since our intention is to identify/tag a browser that was used to authenticate a session without demanding or processing any information pertaining to user’s browser/device. And the solution is very simple, setting a cookie with roughly 32 bytes of randomly generated raw bytes (64 hexadecimal characters) that outlives any session ID or API token used with in your application. This is still the most reliable method to differentiate and detect any login attempt that is coming from a different device/browser other than last authenticated one. In my view, the ideal implementation is where this fingerprint is required as one of the arguments to generate a session/API token. If fingerprint cookie does not already exist in browser, one should be generated and set automatically and then sent alongside any request to API server all the while at backend, there is no real need to store/process”User-Agent”except for once, while generating session/API token by associating this 32 byte cookie value with the session itself. Note that this unique yet random value is meant to outlive any session therefore multiple (but not parallel) sessions should be able to associate with same value.

Leave a Reply

Your email address will not be published. Required fields are marked *

XSS Attacks

XSS Attacks

(And where to store your backend API/Session tokens?

Deploying an Ethereum 1.0 Geth Node

Deploying an Ethereum 1.0 Geth Node

In this article, We are going to deploy an Eth1 geth node on a Ubuntu 22