User-Agent Client Hints are a new expansion to the Client Hints API, that enables developers to access information about a user's browser in a privacy-preserving and ergonomic way.
Client Hints enable developers to actively request information about the user's device or conditions, rather than needing to parse it out of the User-Agent (UA) string. Providing this alternative route is the first step to eventually reducing User-Agent string granularity.
Learn how to update your existing functionality that relies on parsing the User-Agent string to make use of User-Agent Client Hints instead.
Background
When web browsers make requests they include information about the browser and its environment so that servers can enable analytics and customize the response. This was defined all the way back in 1996 (RFC 1945 for HTTP/1.0), where you can find the original definition for the User-Agent string, which includes an example:
User-Agent: CERN-LineMode/2.15 libwww/2.17b3
This header was intended to specify, in order of significance, the product (e.g. browser or library) and a comment (e.g. version).
The state of the User-Agent string
Over the intervening decades, this string has accrued a variety of additional details about the client making the request (as well as cruft, due to backwards compatibility). We can see that when looking at Chrome's current User-Agent string:
Mozilla/5.0 (Linux; Android 10; Pixel 3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4076.0 Mobile Safari/537.36
The above string contains information about the user's operating system and version, the device model, the browser's brand and full version, enough clues to infer it's a mobile browser, and not to mention a number of references to other browsers for historical reasons.
The combination of these parameters with the sheer diversity of possible values means the User-Agent string could contain enough information to allow individual users to be uniquely identified.
The User-Agent string enables many legitimate use cases, and serves an important purpose for developers and site owners. However, it is also critical that users' privacy is protected against covert tracking methods, and sending UA information by default goes against that goal.
There's also a need to improve web compatibility when it comes to the User-Agent string. It is unstructured, so parsing it results in unnecessary complexity, which is often the cause for bugs and site compatibility issues that hurt users. These issues also disproportionately hurt users of less common browsers, as sites may have failed to test against their configuration.
Introducing the new User-Agent Client Hints
User-Agent Client Hints enable access to the same information but in a more privacy-preserving way, in turn enabling browsers to eventually reduce the User-Agent string's default of broadcasting everything. Client Hints enforce a model where the server must ask the browser for a set of data about the client (the hints) and the browser applies its own policies or user configuration to determine what data is returned. This means that rather than exposing all the User-Agent information by default, access is now managed in an explicit and auditable fashion. Developers also benefit from a simpler API - no more regular expressions!
The current set of Client Hints primarily describes the browser's display and connection capabilities. You can explore the details in Automating Resource Selection with Client Hints, but here's a quick refresher on the process.
The server asks for specific Client Hints via a header:
⬇️ Response from server
Accept-CH: Viewport-Width, Width
Or a meta tag:
<meta http-equiv="Accept-CH" content="Viewport-Width, Width" />
The browser can then choose to send the following headers back in subsequent requests:
⬆️ Subsequent request
Viewport-Width: 460
Width: 230
The server can choose to vary its responses, for example by serving images at an appropriate resolution.
User-Agent Client Hints expand the range of properties with the Sec-CH-UA
prefix that can be specified via the Accept-CH
server response header. For all
the details, start with the explainer and
then dive into the full proposal.
User-Agent Client Hints from Chromium 89
User-Agent Client Hints have been default enabled in Chrome since version 89.
By default, the browser returns the browser brand, significant / major version, platform, and an indicator if the client is a mobile device:
⬆️ All requests
Sec-CH-UA: "Chromium";v="93", "Google Chrome";v="93", " Not;A Brand";v="99"
Sec-CH-UA-Mobile: ?0
Sec-CH-UA-Platform: "macOS"
User-Agent response and request headers
⬇️ Response Accept-CH ⬆️ Request header |
⬆️ Request Example value |
Description |
---|---|---|
Sec-CH-UA |
"Chromium";v="84", "Google Chrome";v="84" |
List of browser brands and their significant version. |
Sec-CH-UA-Mobile |
?1 |
Boolean indicating if the browser is on a mobile device (?1 for true) or not (?0 for false). |
Sec-CH-UA-Full-Version |
"84.0.4143.2" |
[Deprecated]The complete version for the browser. |
Sec-CH-UA-Full-Version-List |
"Chromium";v="84.0.4143.2", "Google Chrome";v="84.0.4143.2" |
List of browser brands and their full version. |
Sec-CH-UA-Platform |
"Android" |
The platform for the device, usually the operating system (OS). |
Sec-CH-UA-Platform-Version |
"10" |
The version for the platform or OS. |
Sec-CH-UA-Arch |
"arm" |
The underlying architecture for the device. While this may not be relevant to displaying the page, the site may want to offer a download which defaults to the right format. |
Sec-CH-UA-Model |
"Pixel 3" |
The device model. |
Sec-CH-UA-Bitness |
"64" |
The underlying architecture's bitness (i.e., the size in bits of an integer or memory address) |
Example exchange
An example exchange would look like this:
⬆️ Initial request from browser
The browser is requesting the /downloads
page from the site and sends its default basic User-Agent.
GET /downloads HTTP/1.1
Host: example.site
Sec-CH-UA: "Chromium";v="93", "Google Chrome";v="93", " Not;A Brand";v="99"
Sec-CH-UA-Mobile: ?1
Sec-CH-UA-Platform: "Android"
⬇️ Response from server
The server sends the page back and additionally
asks for the full browser version and the platform.
HTTP/1.1 200 OK
Accept-CH: Sec-CH-UA-Full-Version-List
⬆️ Subsequent requests
The browser grants the server access to the
additional information and sends the extra hints back in all subsequent
requests.
GET /downloads/app1 HTTP/1.1
Host: example.site
Sec-CH-UA: " Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"
Sec-CH-UA-Mobile: ?1
Sec-CH-UA-Full-Version-List: " Not A;Brand";v="99.0.0.0", "Chromium";v="98.0.4738.0", "Google Chrome";v="98.0.4738.0"
Sec-CH-UA-Platform: "Android"
JavaScript API
Alongside the headers, the User-Agent can also be accessed in JavaScript via
navigator.userAgentData
. The default Sec-CH-UA
, Sec-CH-UA-Mobile
, and
Sec-CH-UA-Platform
header information can be accessed via the brands
and
mobile
properties, respectively:
// Log the brand data
console.log(navigator.userAgentData.brands);
// output
[
{
brand: 'Chromium',
version: '93',
},
{
brand: 'Google Chrome',
version: '93',
},
{
brand: ' Not;A Brand',
version: '99',
},
];
// Log the mobile indicator
console.log(navigator.userAgentData.mobile);
// output
false;
// Log the platform value
console.log(navigator.userAgentData.platform);
// output
"macOS";
The additional values are accessed via the getHighEntropyValues()
call. The
"high entropy" term is a reference to information entropy, in other
words - the amount of information that these values reveal about the user's
browser. As with requesting the additional headers, it's down to the browser
what values, if any, are returned.
// Log the full user-agent data
navigator
.userAgentData.getHighEntropyValues(
["architecture", "model", "bitness", "platformVersion",
"fullVersionList"])
.then(ua => { console.log(ua) });
// output
{
"architecture":"x86",
"bitness":"64",
"brands":[
{
"brand":" Not A;Brand",
"version":"99"
},
{
"brand":"Chromium",
"version":"98"
},
{
"brand":"Google Chrome",
"version":"98"
}
],
"fullVersionList":[
{
"brand":" Not A;Brand",
"version":"99.0.0.0"
},
{
"brand":"Chromium",
"version":"98.0.4738.0"
},
{
"brand":"Google Chrome",
"version":"98.0.4738.0"
}
],
"mobile":false,
"model":"",
"platformVersion":"12.0.1"
}
Demo
You can try out both the headers and the JavaScript API on your own device at user-agent-client-hints.glitch.me.
Hint life-time and resetting
Hints specified via the Accept-CH
header will be sent for the duration of the
browser session or until a different set of hints are specified.
That means if the server sends:
⬇️ Response
Accept-CH: Sec-CH-UA-Full-Version-List
Then the browser will send the Sec-CH-UA-Full-Version-List
header on all requests
for that site until the browser is closed.
⬆️ Subsequent requests
Sec-CH-UA-Full-Version-List: " Not A;Brand";v="99.0.0.0", "Chromium";v="98.0.4738.0", "Google Chrome";v="98.0.4738.0"
However, if another Accept-CH
header is received then that will completely
replace the current hints the browser is sending.
⬇️ Response
Accept-CH: Sec-CH-UA-Bitness
⬆️ Subsequent requests
Sec-CH-UA-Platform: "64"
The previously asked-for Sec-CH-UA-Full-Version-List
will not be sent.
It's best to think of the Accept-CH
header as specifying the complete set of
hints desired for that page, meaning the browser then sends the specified hints
for all the subresources on that page. While hints will persist to the next
navigation, the site should not rely or assume they will be delivered.
You can also use this to effectively clear all hints being sent by the browser
by sending a blank Accept-CH
in the response. Consider adding this anywhere
that the user is resetting preferences or signing out of your site.
This pattern also matches how hints work via the
<meta http-equiv="Accept-CH" …>
tag. The requested hints will only be sent on
requests initiated by the page and not on any subsequent navigation.
Hint scope and cross-origin requests
By default, Client Hints will only be sent on same-origin requests. That means
if you ask for specific hints on https://example.com
, but the resources you
want to optimize are on https://downloads.example.com
they will not
receive any hints.
To allow hints on cross-origin requests each hint and origin must be specified
by a Permissions-Policy
header. To apply this to a User-Agent Client Hint, you
need to lowercase the hint and remove the sec-
prefix. For example:
⬇️ Response from example.com
Accept-CH: Sec-CH-UA-Platform-Version, DPR
Permissions-Policy: ch-ua-platform-version=(self "downloads.example.com"),
ch-dpr=(self "cdn.provider" "img.example.com");
⬆️ Request to downloads.example.com
Sec-CH-UA-Platform-Version: "10"
⬆️ Requests to cdn.provider
or img.example.com
DPR: 2
Where to use User-Agent Client Hints?
The quick answer is that you should refactor any instances where you are parsing
either the User-Agent header or making use of any of the JavaScript calls that
access the same information (i.e. navigator.userAgent
, navigator.appVersion
,
or navigator.platform
) to make use of User-Agent Client Hints instead.
Taking this a step further, you should re-examine your use of User-Agent information, and replace it with other methods whenever possible. Often, you can accomplish the same goal by making use of progressive enhancement, feature detection, or responsive design. The base problem with relying on the User-Agent data is that you are always maintaining a mapping between the property you're inspecting and the behavior it enables. It's a maintenance overhead to ensure that your detection is comprehensive and remains up-to-date.
With these caveats in mind, the User-Agent Client Hints repo lists some valid use cases for sites.
What happens to the User-Agent string?
The plan is to minimize the ability for covert tracking on the web by reducing the amount of identifying information exposed by the existing User-Agent string while not causing undue disruption on existing sites. Introducing User-Agent Client Hints now gives you a chance to understand and experiment with the new capability, before any changes are made to User-Agent strings.
Eventually, the information in the User-Agent string will be reduced so it maintains the legacy format while only providing the same high-level browser and significant version information as per the default hints. In Chromium, this change has been deferred until at least 2022 to provide additional time for the ecosystem to evaluate the new User Agent Client Hints capabilities.
You can test a version of this by enabling the
about://flags/#reduce-user-agent
flag from Chrome 93 (Note: this flag was
named about://flags/#freeze-user-agent
in versions Chrome 84 - 92). This will
return a string with the historical entries for compatibility reasons, but with
sanitized specifics. For example, something like:
Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Mobile Safari/537.36
Thumbnail by Sergey Zolkin on Unsplash