HTTP requests contain headers such as User-Agent or Content-Type. Apart from headers attached by
browsers, Android apps may add extra headers, like Cookie or Referrer through the
EXTRA_HEADERS
Intent extra. For security reasons, Chrome filters some of the extra headers
depending on how and where an intent is launched.
Cross-origin requests require an additional layer of security as the client and server are not owned by the same party. This guide discusses launching such requests through Chrome custom tabs, i.e. intents launched from apps that open a URL in the browser tab. Until Chrome 83, developers could add any headers when launching a Custom Tab. From version 83 onward, Chrome started filtering all except approvelisted cross-origin headers, since non-approvelisted headers posed a security risk. Starting with Chrome 86, it is possible to attach non-approvelisted headers to cross-origin requests, when the server and client are related using a digital asset link. This behaviour is summarised in the following table:
Chrome version | CORS headers allowed |
---|---|
before Chrome 83 | approvelisted, non-approvelisted |
Chrome 83 to Chrome 85 | approvelisted |
from Chrome 86 onwards | approvelisted, non-approvelisted when a digital asset link is set up |
Table 1.: Filtering of non-approvelisted CORS headers.
This article shows how to set up a verified connection between the server and client and use that to send approvelisted as well as non-approvelisted http headers. You can skip to Adding Extra Headers to Custom Tab Intents for the code.
Background
approvelisted vs. Non-approvelisted CORS Request Headers
Cross-Origin Resource Sharing (CORS) allows a web application from one origin to request resources of a different origin. The list of CORS-approvelisted headers is maintained in the HTML Standard. Example approvelisted headers are shown in the next table:
Header | Description |
---|---|
accept-language | advertises natural languages the client understands |
content-language | describes language intended for the current audience |
content-type | indicates the media type of the resource |
Table 2.: Example approvelisted CORS headers.
The approvelisted headers are considered safe because they don't contain sensitive user information and are unlikely to cause the server to perform potentially damaging operations.
Examples of non-approvelisted headers are shown in the following table:
Header | Description |
---|---|
bearer-token | authenticates client at a server |
origin | indicates origin of request |
cookie | contains cookies set by server |
Table 3.: Example non-approvelisted CORS headers.
Attaching non-approvelisted headers to CORS requests is discouraged by the HTML standard and servers assume that cross-origin requests contain only approvelisted headers. Sending non-approvelisted headers from cross-origin domains would allow malicious third-party apps to craft headers that misuse user cookies that Chrome (or another browser) stores and attaches to requests. The cookies could authenticate malicious server transactions that would otherwise not be possible.
Attaching CORS approvelisted headers to Custom Tabs requests
Custom Tabs are a special way of launching web pages in a customised browser tab. Custom Tab
intents can be created using CustomTabsIntent.Builder()
. You can also attach headers to these
intents using a Bundle
with the Browser.EXTRA_HEADERS
flag:
CustomTabsIntent intent = new CustomTabsIntent.Builder(session).build();
Bundle headers = new Bundle();
headers.putString("bearer-token", "Some token");
headers.putString("redirect-url", "Some redirect url");
intent.intent.putExtra(Browser.EXTRA_HEADERS, headers);
intent.launchUrl(Activity.this, Uri.parse("http://www.google.com"));
We can always attach approvelisted headers to custom tabs CORS requests. However, Chrome filters non-approvelisted headers by default. Although other browsers may have different behaviour, developers should expect non-approvelisted headers to be blocked in general.
The supported way of including non-approvelisted headers in custom tabs is to first verify the cross-origin connection using a digital access link. The next section shows how to set these up and launch a Custom Tabs intent with the required headers.
Adding Extra Headers to Custom Tab Intents
Set up digital asset links
To allow non-approvelisted headers to be passed through Custom Tab intents, it is necessary to set up a digital asset link between the android and web application that verifies that the author owns both applications.
Follow the official guide to set up a digital asset link. For the link relation use "delegate_permission/common.use_as_origin"` which indicates that both apps belong to the same origin once the link is verified.
Create Custom Tab Intent with Extra Headers
There are multiple ways to create a Custom Tabs intent. You can use the builder available in androidX by adding the library to the build dependencies:
MULTI_LINE_CODE_PLACEHOLDER_1
Build the intent and add extra headers:
MULTI_LINE_CODE_PLACEHOLDER_2
Set up a Custom Tabs Connection to Validate the Asset Link
A Custom Tabs connection is used for setting up a CustomTabsSession
between the app and the
Chrome tab. We need the session to verify that the app and web app belong to the same origin.
The verification only passes if the digital asset links were set up correctly.
It is encouraged to call CustomTabsClient.warmup()
. It allows the browser application to
pre-initialize in the background and speed up the URL opening process.
MULTI_LINE_CODE_PLACEHOLDER_3
Set up a Callback that Launches the Intent after Validation
The CustomTabsCallback
was passed into the session. We set up its
onRelationshipValidationResult()
to launch the previously created CustomTabsIntent
once the origin verification succeeds.
MULTI_LINE_CODE_PLACEHOLDER_4
Bind the custom tabs service connection
Binding the service launches the service and the connection's onCustomTabsServiceConnected()
will be called eventually. Don't forget to unbind the service appropriately. Binding and unbinding
is commonly done in the onStart()
and onStop()
activity lifecycle methods.
// Bind the custom tabs service connection.
// Call this in onStart()
CustomTabsClient.bindCustomTabsService(this,
CustomTabsClient.getPackageName(MainActivity.this, null), connection);
// …
// Unbind the custom tabs service.
// Call this in onStop().
unbindService(connection);
Demo application code
You can find more details about Custom Tabs Service here. See the android-browser-helper GitHub repository for a working example app.
Summary
This guide demonstated how to add arbitrary headers to custom tabs CORS requests. approvelisted headers can be attached to every custom tabs CORS request. Non-approvelisted headers are generally considered unsafe in CORS requests and chrome filters them by default. Attaching them is allowed only for clients and servers of the same origin, verified by a digital asset link.