Learn about User-Agent & Client Hints API support in Chrome, Safari & Firefox and how we use them for passkeys & device detection in JavaScript components.
Vincent
Created: July 2, 2024
Updated: September 10, 2024
Our mission is to make the Internet a safer place, and the new login standard passkeys provides a superior solution to achieve that. That's why we want to help you understand passkeys and its characteristics better.
1.1 A Brief History of the User-Agent
1.2 User-Agent Reduction & Client Hints Introduction
1.2.1 User-Agents in Safari
1.2.2 User-Agents in Chrome (+ Edge)
1.2.3 User-Agents in Firefox
1.2.4 User-Agents Today (July 2024)
2.1 What's a Classical User-Agent?
2.1.1 Why Use User-Agent for Feature Detection?
2.1.2 Understanding Entropy in Fingerprinting: Why is Providing Much Information a Problem?
2.2 Reduction of User-Agent Entropy
2.2.1 Example 1: Chrome User-Agent Reduction
2.2.2 Example 2: Firefox User-Agent Reduction
2.2.3 Example 3: Safari User-Agent Reduction on macOS
2.2.4 Example 4: Safari User-Agent on iOS (Not Reduced)
2.3 Detection Limitations with User-Agent Reduction
3.1 Which Client Hints Do Exist?
3.2 How to Access Client Hints via HTTP Request Headers
3.2.1 How to Use Regular Client Hint Headers?
3.2.2 How to use Critical (Client) Hint Headers?
3.3 How to Access Client Hints via JavaScript API (Embedded Scripts)
Methods for detecting and responding to different user environments are constantly changing. Traditionally, User-Agent strings have been the way for identifying browser and device information, allowing websites to tailor experiences accordingly. However, these strings have often been used for fingerprinting and identifying users without their consent for marketing reasons raising privacy concerns.
In this context, Client Hints as JavaScript APIs have emerged as a tool, providing a more controlled and privacy-respecting way to share necessary information about the user's device and preferences. This article concentrates on the consequences of User-Agent reduction and explores how Client Hints help adapt to this new situation.
Key points we will address:
As browsers and operating systems have different stands and adapt their approaches differently, software developers face the challenge of ensuring their applications can still detect and respond to varying user environments accurately across browsers and operating systems.
This article aims to provide a comprehensive overview of these changes, offering insights and practical guidance to help developers in case their use case or software depends on device or operating systems details (e.g. detailed device management, detect known devices, fraud prevention or other feature detections) or like in our own case optimize passkey experience with User-Agent and Client Hints.
Before we dive into the technical details about Client Hints, we’ll briefly explore the history of the User-Agent and how the different User-Agent reduction efforts work.
The history of User-Agent strings can be traced back to the inception of early web browsers. Tim Berners-Lee developed the first web browser, WorldWideWeb (later renamed Nexus) in 1990. This was soon followed by other pioneering browsers like Line Mode Browser in 1991, and subsequently, browsers such as MidasWWW, ViolaWWW, Erwise, and Cello emerged.
In 1993, the release of NCSA Mosaic, often simply referred to as Mosaic, is credited with igniting the initial surge in web popularity. Mosaic’s User-Agent string was quite straightforward, typically formatted as NCSA_Mosaic/1.0
featuring the product name followed by an optional slash and version number.
Initially, the User-Agent field was introduced for analytical purposes and to help identify issues. It was explicitly recommended that this field "should be included" in HTTP requests, as noted in the W3C HTTP archive from 1992. This straightforward field provided a simple yet effective way to convey the product name and version, aiding in the understanding and troubleshooting of browser-related issues.
As the web evolved, so did the complexity and usage of User-Agent strings. They became more detailed and began to include a wealth of information about the browser, operating system, and device. While this information was valuable for web analytics and optimizing user experiences, it also posed significant privacy concerns. Detailed User-Agent strings allowed for device fingerprinting, enabling advertisers and trackers to create unique profiles of users and track their online activities across different websites.
In the modern era, where privacy has become an important concern for users and regulators alike, the detailed nature of User-Agent strings has increasingly been viewed as problematic. This realization has led to efforts to reduce the granularity of User-Agent strings, aiming to strike a balance between providing necessary information for web functionality and protecting user privacy. This transition marks the beginning of User-Agent reduction, a movement towards minimizing the information shared in User-Agent strings to mitigate privacy risks and enhance security.
The movement towards User-Agent reduction began as a response to growing privacy concerns. Here is a chronological overview of key milestones in this evolution:
In 2017, Apple initiated the User-Agent reduction movement with a famous Twitter post announcing that Safari would freeze the User-Agent string in Safari Technology Preview 46 (STP 46), aiming to combat fingerprinting and improve privacy.
However, this decision was later partially reversed in 2021 due to significant compatibility issues with websites relying on up-to-date User-Agent information.
In 2019/2020, Google announced plans to reduce the granularity of User-Agent strings in Chrome, introducing User-Agent Client Hints (UA-CH) as a flexible and controlled way to request specific browser and device information. Google began testing User-Agent reduction in Chrome Canary and Beta versions, gathering feedback to ensure compatibility and functionality and was completed in 2023 for Android and all other Chrome platforms. At the same time, Chrome introduced Client Hints as a new way to access that information. As Microsoft Edge is based on Chromium, Microsoft Edge behaves like Chrome on all platforms.
In 2021, Mozilla joined the effort, gradually reducing the User-Agent string granularity but Mozilla decided to not support Client Hints in Firefox.
User-Agent Reduction is complete: The transitions to reduce User-Agents is complete on all major browsers.
Client Hints have evolved within Chromium: All browsers still send a User-Agent header, but in most of them it’s frozen to an older version (see table below). For Chromium, Client Hints can be used to get more information.
Let’s look what browser currently send as User-Agents in the following chapter.
Subscribe to our Passkeys Substack for the latest news, insights and strategies.
SubscribeWhen talking about User-Agent Reduction what is meant to reduce the information within the User-Agent string. Let’s dive deeper to find out what was embedded into the User-Agent prior to reduction.
A classical User-Agent string typically included the following components, that could be used to detect functionality:
Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.2.1.0 Mobile Safari/537.36
Become part of our Passkeys Community for updates and support.
JoinHistorically, User-Agent strings were often used for feature and platform detection, allowing websites to tailor their content and functionality based on the detected browser and device characteristics (e.g. determine which download for specific binary to offer). This became especially important with the rise of mobile phones and tablets for layouts that were not built responsive to be able to redirect mobiles phones to their specific version.
However, this approach is now considered suboptimal, as it relies on assumptions about browser capabilities that may not hold true or be not specific enough. Modern web development practices advocate for using** feature detection via browser APIs** where possible to directly check for specific browser features and capabilities. This method is more reliable and ensures that websites work correctly regardless of the User-Agent string or browser version. Feature detection focuses on the actual capabilities of the browser, providing a more robust and future-proof way to handle differences in browser behavior and functionality.
Up to today, this is not fully possible as new browser APIs to detect functionality are implemented at different times and often there is a different approach between the larger (e.g. Chromium-based browsers) and smaller browser teams (e.g. Firefox).
More information gives a website a wider possibility to create a fingerprint with more “entropy”. Entropy in this context is the amount of variation that can be used to generate a unique fingerprint among different visitors from a website.
A fingerprint can be used for different purposes. It can be used for protective purposes like detecting fraud and stopping account takeovers by identifying new devices, but it can also be used to identify visitors without their consent or for more specific ad targeting.
In the context of User-Agent strings, entropy refers to the amount of unique, identifiable information that can be used to track and distinguish individual users.
There is a lot of further information that can be used as a source of entropy beneath User-Agent. For example, you can find different sources of entropy on https://coveryourtracks.eff.org.
Therefore, when browser talk about User-Agent reduction, they primarily target to reduce the high entropy information. Here are different examples for the reduction:
In the following, you find an example for Google’s Chrome User-Agent reduction on an Android phone:
Before:
Mozilla/5.0 (Linux; Android <span style={{color: '#016F01' }}>13; Pixel 7</span>) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.0.0 Mobile Safari/537.36
After:
Mozilla/5.0 (Linux; Android <span style={{color: '#016F01' }}>10; K</span>) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.0.0 Mobile Safari/537.36
The format of the User-Agent remained unchanged in order not to interfere with existing parsing logic of libraries. The following values were reduced:
The graphic details which values will continue to be updates based on the operating system and the green values show which values will be constant on all platforms. For different operating system values there will be only a variant available that stays constant:
Operating system | Reduced/fixed name |
---|---|
Mac | Macintosh; Intel Mac OS X 10_15_7 |
Windows | Windows NT 10.0; Win64; x64 |
ChromeOS | X11; CrOS x86_64 14541.0.0 |
Linux | X11; Linux x86_64 |
Android | Linux; Android 10; K |
iOS | not reduced |
In the following, you find an example for Mozilla Firefox’s User-Agent reduction that follows most of Google’s positions on User-Agent reduction.
Here is an example of a User-Agent on Windows 11 with a current Firefox version:
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0
In this User-Agent, the CPU Architecture, the operating system version and browser minor version have been frozen.
In the following, you find an example of a Safari User-Agent reduction on macOS.
First, let’s start with a Safari User-Agent on a macOS Sonoma Version 14.5 on Mac Silicon M2.
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15
You can see that the macOS Version is capped to 10_15_07
and the architecture is also fixed to Intel
not reporting the Mac CPU architecture. This is also true for Firefox and Chrome on macOS.
In contrast to the example provided for macOS, on iOS, Safari continues to expose the correct operating system version number (17_5_1
):
Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1
Chrome and Firefox show a comparable behavior on iOS exposing the actual operating system version. :
Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/126.0.6478.153 Mobile/15E148 Safari/604.1
Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/127.1 Mobile/15E148 Safari/605.1.15
It seems that this is due to the large backlash Safari had received on changing User-Agent in 2017 on iOS that Apple refrains from adjusting the operating system version on iOS.
Overall, it is obvious that the User-Agent is reduced specifically on high entropy information. The following overview shows on which platform / operating system combinations the User-Agent is reduced.
Browser | Windows | MacOS | iOS | Android |
---|---|---|---|---|
Chrome | reduced | reduced | not reduced | reduced |
Edge | reduced | reduced | not reduced | reduced |
Firefox | reduced | reduced | not reduced | reduced |
Safari | - | reduced | not reduced | - |
WebViews | - | - | not reduced | not reduced |
Therefore, the most important detection problems with a reduced User-Agent are:
There are other ways to detect operating systems and browsers based on elaborated fingerprinting techniques, like TLS fingerprinting. As there are legitimate scenarios where a deeper operating system detection would make sense, Google introduced Client Hints to be able to receive this information programmatically. We will see how Client Hints work in the next chapter.
Client Hints enable websites to access high entropy information that was removed from the User-Agent string in a privacy-friendly manner. There are two primary methods to access Client Hints:
Client hints can also be used to retrieve the low entropy information that is already included in the User-Agent. This implies that on Chrome, where client hints are supported, user-agents are not needed anymore.
Depending on which access method is used, the naming of the Client hints slightly differs. We have listed the most important name of Client Hints:
HTTP UA-CH token | UA-CH JS API | Entropy |
---|---|---|
Sec-CH-UA-Platform-Version | UADataValues.platformVersion | High |
Sec -CH-UA-Mobile | NavigatorUAData.mobile | Low |
Sec-CH-UA-Model | UADataValues.model | High |
Sec-CH-UA | NavigatorUAData.brands | Low |
Sec-CH-UA-Arch | UADataValues. architecture | High |
In a first-party context, websites can request specific information about the user's browser and device using HTTP request headers. This approach involves setting the correct header in the server's HTTP response, indicating which Client Hints the server is interested in. The browser then includes these hints in subsequent requests to the same origin.
This header signals the browser to send detailed user information in future requests, allowing the server to tailor responses accordingly. This method ensures that detailed user information is only accessible to the first-party website, preventing third-party resources from obtaining high entropy data. There are two types of request headers available which we will discuss now:
Regular Client Hints are headers that a server can request from the browser to gather information about the user's environment in subsequent requests.
Example: Chrome on macOS 14.5 with regular Client Hints on corbado.com
Header | Value |
---|---|
Sec-Ch-Ua | "Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24" |
Sec-Ch-Ua-Mobile | ?0 |
Sec-Ch-Ua-Platform | "macOS" |
Header | Value |
---|---|
Accept-CH | Sec-CH-UA-Platform-Version |
Header | Value |
---|---|
Sec-Ch-Ua | "Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24" |
Sec-Ch-Ua-Mobile | ?0 |
Sec-Ch-Ua-Platform | "macOS" |
Sec-Ch-Ua-Platform | "14.5.0" |
At the same time, the User-Agent for this request will remain the same (without platform version):
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Using https://user-agent-client-hints.glitch.me/headers, you can actually play around how different layouts of information would look like for regular hints (keep in mind you must use Chrome). See here for more details. In case corbado.com would need this information on the very first page request, this using regular Client Hints would not be sufficient, but there is a way to expedite this process with critical (client) hints.
Lets say corbado.com would need the platform-version on the first request to render an appropriate download page tailored to the exact macOS version. It could use the Critical (client) hints which would lead to an immedate retry of the request to only render the page including the header:
Example: Chrome on macOS 14.5 with critical Client Hints on corbado.com
Header | Value |
---|---|
Sec-CH-UA | "Google Chrome";v="125", "Chromium";v="125", "Not_A/Brand";v="24" |
Sec-CH-UA-Mobile | ?0 |
Sec-CH-UA-Platform | "macOS" |
Header | Value |
---|---|
Accept-CH | Sec-CH-UA-Platform-Version |
Header | Value |
---|---|
Sec-CH-UA | "Google Chrome";v="125", "Chromium";v="125", "Not_A/Brand";v="24" |
Sec-CH-UA-Mobile | ?0 |
Sec-CH-UA-Platform | "macOS" |
Sec-CH-UA-Platform-Version | "14.5.0" |
In the following charts, we see another example where the website requests the device model as critical part with the Critical-CH header. As we can see, the client retries the request like in the example above (left sequence chart).
Client hints with Chromium: https://developers.google.com/privacy-sandbox/protections/User-Agent
Using critical Client Hints therefore adds latency because the browser must start a second request immediately adding roundtrips. There is a way to optimize the transfer by facilitating the TLS handshake (the green box at the top of the chart) to already transmit the details. This approach facilitating ALPN (Application-Layer Protocol Negotiation) can be found here in detail but is beyond the purpose of this article.
To receive additional information using headers, configuration needs to be added either to the backend or to the webserver / load balancers. This is easily done in a first-party context, when you have full control over the website as company. For example, our UI components at corbado.com require the platform version to increase the precision of our passkey intelligence. Developers integrate our components on various pages and requiring them to add headers and then pass that information to our components would be an unpleased overhead. For SPAs and other embedded JavaScript applications, there is another interface that can be used, which will be explained in the next section.
Using the navigator.userAgentData.getHighEntropyValues() function, Client hints can also be accessed via a JavaScript API, providing a flexible and dynamic way to request specific information about the user's browser and device. This method involves using the navigator.userAgentData
object within the web page's script to query for Client Hints. This method does not require the Client hint headers to be set in any way, it just always works.
For example, when executing this code in a Chrome Console on macOS:
When the promise resolves, the relevant information is returned. As we outlined above, the names are not completely identical to the headers but are very close.
The platformVersion
is then returned in the promise and it can be accessed directly from the embedded JavaScript code. The coverage for this function is much lower than the actual User-Agent reduction low:
Additionally, Chrome does not offer this functionality on iOS because it deploys WebKit on iOS due to Apple's limitations.
We now have put together the most important facts about User-Agent reduction and how Client hints offer a way around it in Chrome.
Depending on your use case, you should use a different approach to ensure your application gets the best result possible:
By choosing the appropriate method based on your specific needs, you can effectively balance the need for detailed user information with privacy and performance considerations. If you highly depend on information that is difficult to extract or is really device based fall back to professional libraries like wurfl or 51degrees.com that do the heavy lifting for you. Both libraries support integrating client links into the detection and also have their own propriatry way to even detect iPhone models.
At Corbado, we focus on developing solutions around passkeys using UI components primarily developed with React. Our components are embedded into various websites, so we follow our own recommendation and use getHighEntropyValues()wherever it is available, falling back on classic User-Agent parsing for all other cases. We primarily use this for:
More details on: https://learn.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11
When we started compiling information for our components, we found a lot of fragmented data created during or before the start of User-Agent reduction. Therefore, we thought a summary would help developers who want to improve detection but do not work with User-Agents or Client Hints regularly.
After user User-Agent based detection was the standard for decades User-Agent strings have become less detailed to protect user privacy. In this article we covered:
Client hints are continuing to evolve, and their adoption may increase as more browsers and developers recognize their benefits. However, it remains to be seen whether their support will become widespread or if the web will continue to diverge on this topic.
Table of Contents
Enjoyed this read?
🤝 Join our Passkeys Community
Share passkeys implementation tips and get support to free the world from passwords.
🚀 Subscribe to Substack
Get the latest news, strategies, and insights about passkeys sent straight to your inbox.
We provide UI components, SDKs and guides to help you add passkeys to your app in <1 hour
Start for free