Agrega encabezados de solicitud HTTP adicionales

Las solicitudes HTTP contienen encabezados como User-Agent o Content-Type. Además de los encabezados que adjuntan los navegadores, las apps para Android pueden agregar encabezados adicionales, como Cookie o Referrer, a través del elemento adicional EXTRA_HEADERS del Intent. Por motivos de seguridad, Chrome filtra algunos de los encabezados adicionales según cómo y dónde se inicie una intención.

Las solicitudes de origen cruzado requieren una capa de seguridad adicional, ya que el cliente y el servidor no son propiedad de la misma parte. En esta guía, se explica cómo iniciar esas solicitudes a través de pestañas personalizadas de Chrome, es decir, intents que se inician desde apps que abren una URL en la pestaña del navegador. Hasta Chrome 83, los desarrolladores podían agregar cualquier encabezado cuando iniciaban una pestaña personalizada. A partir de la versión 83, Chrome comenzó a filtrar todos los encabezados de origen cruzado, excepto los incluidos en la lista de aprobación, ya que los encabezados no incluidos en la lista de aprobación representaban un riesgo de seguridad. A partir de Chrome 86, es posible adjuntar encabezados que no están en la lista de aprobación a las solicitudes de origen cruzado cuando el servidor y el cliente están relacionados a través de un vínculo de activo digital. Este comportamiento se resume en la siguiente tabla:

Versión de Chrome Encabezados de CORS permitidos
antes de Chrome 83 aprobada, no aprobada
Chrome 83 a Chrome 85 incluido en la lista de entidades permitidas
A partir de Chrome 86 Aprobada en la lista, no aprobada en la lista cuando se configura una vinculación de activos digitales

Tabla 1: Se filtran los encabezados de CORS que no están en la lista de entidades aprobadas.

En este artículo, se muestra cómo configurar una conexión verificada entre el servidor y el cliente, y cómo usarla para enviar encabezados HTTP incluidos en la lista de aprobación y no incluidos en ella. Puedes ir directamente a Cómo agregar encabezados adicionales a intents de pestañas personalizadas para ver el código.

Fondo

Encabezados de solicitudes CORS incluidos en la lista de aprobación y no incluidos en la lista de aprobación

El uso compartido de recursos entre dominios (CORS) permite que una aplicación web de un origen solicite recursos de un origen diferente. La lista de encabezados aprobados por CORS se mantiene en el estándar de HTML. En la siguiente tabla, se muestran ejemplos de encabezados incluidos en la lista de aprobación:

Encabezado Descripción
accept-language Anuncia los idiomas naturales que comprende el cliente.
content-language Describe el lenguaje que se pretende usar para el público actual.
content-type indica el tipo de medio del recurso

Tabla 2: Ejemplo de encabezados CORS incluidos en la lista de aprobados.

Los encabezados incluidos en la lista de aprobación se consideran seguros porque no contienen información sensible del usuario y es poco probable que hagan que el servidor realice operaciones potencialmente dañinas.

En la siguiente tabla, se muestran ejemplos de encabezados que no están en la lista de aprobación:

Encabezado Descripción
bearer-token Autentica al cliente en un servidor
origin Indica el origen de la solicitud.
galleta Contiene las cookies establecidas por el servidor.

Tabla 3: Ejemplo de encabezados CORS que no están en la lista de aprobación.

El estándar HTML desaconseja adjuntar encabezados que no estén en la lista de aprobación a las solicitudes de CORS, y los servidores suponen que las solicitudes de origen cruzado solo contienen encabezados que estén en la lista de aprobación. El envío de encabezados que no están en la lista de aprobación desde dominios de origen cruzado permitiría que las apps de terceros maliciosas creen encabezados que hagan un uso inadecuado de las cookies del usuario que Chrome (o cualquier otro navegador) almacena y adjunta a las solicitudes. Las cookies podrían autenticar transacciones maliciosas del servidor que, de otro modo, no serían posibles.

Cómo adjuntar encabezados incluidos en la lista de aprobación de CORS a las solicitudes de pestañas personalizadas

Las pestañas personalizadas son una forma especial de iniciar páginas web en una pestaña del navegador personalizada. Los intents de pestañas personalizadas se pueden crear con CustomTabsIntent.Builder(). También puedes adjuntar encabezados a estas intenciones con un Bundle y la marca Browser.EXTRA_HEADERS:

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"));

Siempre podemos adjuntar encabezados incluidos en la lista de aprobación a las solicitudes de CORS de pestañas personalizadas. Sin embargo, Chrome filtra los encabezados que no están en la lista de aprobación de forma predeterminada. Si bien otros navegadores pueden tener un comportamiento diferente, los desarrolladores deben esperar que los encabezados que no estén en la lista de aprobación se bloqueen en general.

La forma admitida de incluir encabezados que no están en la lista de aprobación en pestañas personalizadas es verificar primero la conexión de origen cruzado con un vínculo de acceso digital. En la siguiente sección, se muestra cómo configurar estos encabezados y lanzar un intent de pestañas personalizadas con los encabezados requeridos.

Cómo agregar encabezados adicionales a intents de pestañas personalizadas

Para permitir que los encabezados que no están en la lista de aprobación se pasen a través de intents de pestañas personalizadas, es necesario configurar un vínculo de activo digital entre la aplicación para Android y la aplicación web que verifique que el autor es propietario de ambas aplicaciones.

Sigue la guía oficial para configurar una vinculación de activos digitales. Para la relación de vínculo, usa "delegate_permission/common.use_as_origin", que indica que ambas apps pertenecen al mismo origen una vez que se verifica el vínculo.

Crea un intent de pestaña personalizada con encabezados adicionales

Existen varias formas de crear un intent de pestañas personalizadas. Puedes usar el compilador disponible en AndroidX agregando la biblioteca a las dependencias de compilación:

implementation 'androidx.browser:browser:1.2.0'

Crea la intención y agrega encabezados adicionales:

CustomTabsIntent constructExtraHeadersIntent(CustomTabsSession session) {
    CustomTabsIntent intent = new CustomTabsIntent.Builder(session).build();

    // Example non-cors-approvelisted headers.
    Bundle headers = new Bundle();
    headers.putString("bearer-token", "Some token");
    headers.putString("redirect-url", "Some redirect url");
    intent.intent.putExtra(Browser.EXTRA_HEADERS, headers);
    return intent;
}

Se usa una conexión de pestañas personalizadas para configurar un CustomTabsSession entre la app y la pestaña de Chrome. Necesitamos la sesión para verificar que la app y la app web pertenezcan al mismo origen. La verificación solo se aprueba si los vínculos de recursos digitales se configuraron correctamente.

Te recomendamos que llames al CustomTabsClient.warmup(). Permite que la aplicación del navegador se preinicialice en segundo plano y acelere el proceso de apertura de URLs.

// Set up a connection that warms up and validates a session.
CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
    @Override
    public void onCustomTabsServiceConnected(@NonNull ComponentName name, 
        @NonNull CustomTabsClient client) {
        // Create session after service connected.
        mSession = client.newSession(callback);
        client.warmup(0);
        // Validate the session as the same origin to allow cross origin headers.
        mSession.validateRelationship(CustomTabsService.RELATION_USE_AS_ORIGIN, 
            Uri.parse(url), null);
    }
    @Override
    public void onServiceDisconnected(ComponentName componentName) { }
};

Configura una devolución de llamada que inicie el intent después de la validación

Se pasó el CustomTabsCallback a la sesión. Configuramos su onRelationshipValidationResult() para iniciar el CustomTabsIntent creado anteriormente una vez que se realiza correctamente la verificación del origen.

// Set up a callback that launches the intent after session validated.
CustomTabsCallback callback = new CustomTabsCallback() {
    @Override
    public void onRelationshipValidationResult(int relation, @NonNull Uri requestedOrigin, 
        boolean result, @Nullable Bundle extras) {
        // Launch custom tabs intent after session was validated as the same origin.
        CustomTabsIntent intent = constructExtraHeadersIntent(mSession);
        intent.launchUrl(MainActivity.this, Uri.parse(url));
    }
};

Vincula la conexión del servicio de pestañas personalizadas

La vinculación del servicio inicia el servicio y, finalmente, se llamará al onCustomTabsServiceConnected() de la conexión. No olvides desvincular el servicio de forma adecuada. La vinculación y la desvinculación se realizan comúnmente en los métodos de ciclo de vida de la actividad onStart() y onStop().

// 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);

Código de la aplicación de demostración

Puedes encontrar más detalles sobre el servicio de pestañas personalizadas aquí. Consulta el repositorio de GitHub de android-browser-helper para ver una app de ejemplo que funciona.

Resumen

En esta guía, se demostró cómo agregar encabezados arbitrarios a las solicitudes de CORS de pestañas personalizadas. Los encabezados incluidos en la lista de aprobación se pueden adjuntar a cada solicitud de CORS de pestañas personalizadas. Por lo general, los encabezados que no están en la lista de aprobación se consideran inseguros en las solicitudes de CORS, y Chrome los filtra de forma predeterminada. Solo se permite adjuntarlos para los clientes y servidores del mismo origen, verificados por un vínculo de activo digital.