Migrar para as dicas de cliente do user agent

Estratégias para migrar seu site da dependência da string do user agent para as novas dicas de cliente HTTP do user agent.

A string do user agent é uma plataforma de impressão digital passiva significativa em navegadores, além de ser difícil de processar. No entanto, há vários motivos válidos para coletar e processar dados do user agent. Portanto, o que é necessário é um caminho para uma solução melhor. As dicas de cliente HTTP do user agent oferecem uma maneira explícita de declarar sua necessidade de dados e métodos do user agent para retornar os dados em um formato fácil de usar.

Neste artigo, vamos mostrar como auditar seu acesso aos dados do user agent e migrar o uso da string dele para as dicas de cliente do user agent.

Coleta e uso de dados do user agent

Como acontece com qualquer forma de coleta de dados, é preciso entender sempre por que você está coletando esses dados. A primeira etapa, independentemente de você realizar ou não qualquer ação, é entender onde e por que você está usando dados do user agent.

Se você não souber se ou onde os dados do user agent estão sendo usados, pesquise seu código de front-end para usar navigator.userAgent e seu código de back-end para usar o cabeçalho HTTP User-Agent. Verifique também seu código de front-end para usar recursos já descontinuados, como navigator.platform e navigator.appVersion.

De um ponto de vista funcional, pense em qualquer lugar do código em que você esteja gravando ou processando:

  • Nome ou versão do navegador
  • Nome ou versão do sistema operacional
  • Marca ou modelo do dispositivo
  • tipo de CPU, arquitetura ou quantidade de bits (por exemplo, 64 bits)

Também é provável que você esteja usando uma biblioteca ou um serviço de terceiros para processar o user agent. Nesse caso, verifique se eles estão sendo atualizados para oferecer suporte a dicas de cliente HTTP do user agent.

Você está usando apenas dados básicos do user agent?

O conjunto padrão de dicas de cliente HTTP do user agent inclui:

  • Sec-CH-UA: nome do navegador e versão principal/significativa.
  • Sec-CH-UA-Mobile: valor booleano que indica um dispositivo móvel.
  • Sec-CH-UA-Platform: nome do sistema operacional
    • Isso foi atualizado na especificação e será refletido no Chrome e em outros navegadores baseados no Chromium em breve.

A versão reduzida da string do user agent proposta também vai manter essas informações básicas de maneira compatível com versões anteriores. Por exemplo, em vez de Chrome/90.0.4430.85, a string incluiria Chrome/90.0.0.0.

Se você estiver verificando apenas a string do user agent para o nome do navegador, a versão principal ou o sistema operacional, seu código continuará funcionando, mas é provável que você receba avisos de descontinuação.

Embora você possa e deva migrar para as dicas de cliente HTTP do user agent, você pode ter restrições de código ou recursos legados que impedem isso. A redução de informações na string do user agent dessa maneira compatível com versões anteriores tem como objetivo garantir que o código atual receba informações menos detalhadas, mas ainda mantenha a funcionalidade básica.

Estratégia: API JavaScript sob demanda do lado do cliente

Se você estiver usando navigator.userAgent, faça a transição para preferir navigator.userAgentData antes de voltar à análise da string do user agent.

if (navigator.userAgentData) {
  // use new hints
} else {
  // fall back to user-agent string parsing
}

Se você estiver verificando dispositivos móveis ou computadores, use o valor booleano mobile:

const isMobile = navigator.userAgentData.mobile;

userAgentData.brands é uma matriz de objetos com as propriedades brand e version em que o navegador pode listar a compatibilidade com essas marcas. É possível acessá-lo diretamente como uma matriz ou usar uma chamada some() para verificar se uma entrada específica está presente:

function isCompatible(item) {
  // In real life you most likely have more complex rules here
  return ['Chromium', 'Google Chrome', 'NewBrowser'].includes(item.brand);
}
if (navigator.userAgentData.brands.some(isCompatible)) {
  // browser reports as compatible
}

Se você precisar de um dos valores de user agent mais detalhados e de alta entropia, será preciso especificá-lo e verificar o resultado no Promise retornado:

navigator.userAgentData.getHighEntropyValues(['model'])
  .then(ua => {
    // requested hints available as attributes
    const model = ua.model
  });

Essa estratégia também pode ser útil se você quiser migrar do processamento do lado do servidor para o processamento do cliente. A API JavaScript não exige acesso a cabeçalhos de solicitação HTTP. Portanto, os valores do user agent podem ser solicitados a qualquer momento.

Estratégia: cabeçalho estático do lado do servidor

Se você estiver usando o cabeçalho de solicitação User-Agent no servidor e suas necessidades para esses dados forem relativamente consistentes em todo o site, será possível especificar as dicas de cliente desejadas como um conjunto estático nas suas respostas. Essa é uma abordagem relativamente simples, porque geralmente você só precisa configurá-la em um local. Por exemplo, isso pode estar na configuração do servidor da Web se você já tiver adicionado cabeçalhos, a configuração de hospedagem ou a configuração de nível superior da estrutura ou plataforma usada para o site.

Considere essa estratégia se você estiver transformando ou personalizando as respostas veiculadas com base nos dados do user agent.

Navegadores ou outros clientes podem fornecer dicas padrão diferentes. Portanto, é recomendável especificar tudo o que você precisa, mesmo que ele seja fornecido por padrão.

Por exemplo, os padrões atuais do Chrome seriam representados como:

⬇️ Cabeçalhos de resposta

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA

Se você também quiser receber o modelo do dispositivo nas respostas, envie:

⬇️ Cabeçalhos de resposta

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA

Ao processar isso no lado do servidor, primeiro verifique se o cabeçalho Sec-CH-UA desejado foi enviado e, em seguida, substitua pela análise de cabeçalho User-Agent se ele não estiver disponível.

Estratégia: delegar dicas a solicitações de diferentes origens

Se você estiver solicitando sub-recursos de origem cruzada ou entre sites que exigem o envio de dicas de cliente HTTP do user agent nas solicitações, será necessário especificar explicitamente as dicas desejadas usando uma política de permissões.

Por exemplo, digamos que https://blog.site hospede recursos em https://cdn.site, que pode retornar recursos otimizados para um dispositivo específico. https://blog.site pode pedir a dica Sec-CH-UA-Model, mas precisa delegá-la explicitamente a https://cdn.site usando o cabeçalho Permissions-Policy. A lista de dicas controladas por políticas está disponível no rascunho de infraestrutura de dicas de clientes (em inglês)

⬇️ Resposta de blog.site delegando a dica

Accept-CH: Sec-CH-UA-Model
Permissions-Policy: ch-ua-model=(self "https://cdn.site")

⬆️ A solicitação de sub-recursos em cdn.site inclui a dica delegada

Sec-CH-UA-Model: "Pixel 5"

Você pode especificar várias dicas para diversas origens, e não apenas no intervalo ch-ua:

⬇️ Resposta de blog.site delegando várias dicas a várias origens

Accept-CH: Sec-CH-UA-Model, DPR
Permissions-Policy: ch-ua-model=(self "https://cdn.site"),
                    ch-dpr=(self "https://cdn.site" "https://img.site")

Estratégia: delegar dicas a iframes

Os iframes de origem cruzada funcionam de maneira semelhante aos recursos de origem cruzada, mas você especifica as dicas que quer delegar no atributo allow.

⬇️ Resposta de blog.site

Accept-CH: Sec-CH-UA-Model

↪️ HTML de blog.site

<iframe src="https://widget.site" allow="ch-ua-model"></iframe>

⬆️ Solicitação para widget.site

Sec-CH-UA-Model: "Pixel 5"

O atributo allow no iframe substitui qualquer cabeçalho Accept-CH que o widget.site enviar. Portanto, verifique se você especificou tudo o que o site com iframe precisa.

Estratégia: dicas dinâmicas do lado do servidor

Se você tem partes específicas da jornada do usuário em que precisa de uma seleção maior de dicas do que no restante do site, pode solicitar essas dicas sob demanda, e não estaticamente em todo o site. Isso é mais complexo de gerenciar, mas se você já definiu cabeçalhos diferentes para cada rota, isso pode ser viável.

É importante lembrar que cada instância do cabeçalho Accept-CH substituirá efetivamente o conjunto existente. Portanto, se você estiver definindo o cabeçalho dinamicamente, cada página precisará solicitar o conjunto completo de dicas necessárias.

Por exemplo, é possível ter uma seção no seu site em que você quer fornecer ícones e controles que correspondem ao sistema operacional do usuário. Para isso, convém extrair ainda mais o Sec-CH-UA-Platform-Version para exibir sub-recursos adequados.

⬇️ Cabeçalhos de resposta para /blog

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA

⬇️ Cabeçalhos de resposta para /app

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version, Sec-CH-UA

Estratégia: dicas do lado do servidor necessárias na primeira solicitação

Pode haver casos em que você precisa de mais dicas do que o conjunto padrão na primeira solicitação. No entanto, isso provavelmente será raro, portanto, analise o raciocínio.

A primeira solicitação realmente significa a primeira solicitação de nível superior para essa origem enviada naquela sessão de navegação. O conjunto padrão de dicas inclui o nome do navegador com a versão principal, a plataforma e o indicador de dispositivos móveis. A pergunta a ser feita é: você precisa de dados estendidos no carregamento inicial da página?

Para obter dicas adicionais na primeira solicitação, há duas opções. Primeiro, é possível usar o cabeçalho Critical-CH. Ele tem o mesmo formato de Accept-CH, mas informa ao navegador que precisa repetir a solicitação imediatamente se a primeira for enviada sem a dica crítica.

⬆️ Solicitação inicial

[With default headers]

⬇️ Cabeçalhos de resposta

Accept-CH: Sec-CH-UA-Model
Critical-CH: Sec-CH-UA-Model

🔃 O navegador tenta novamente a solicitação inicial com o cabeçalho extra

[With default headers + …]
Sec-CH-UA-Model: Pixel 5

Isso vai gerar sobrecarga na nova tentativa na primeira solicitação, mas o custo de implementação é relativamente baixo. Envie o cabeçalho extra, e o navegador fará o resto.

Para situações em que você realmente precisa de mais dicas no primeiro carregamento de página, a proposta de confiabilidade de dicas do cliente define uma maneira de especificar dicas nas configurações no nível da conexão. Isso faz uso da extensão Application-Layer Protocol Settings(ALPS) para o TLS 1.3 a fim de permitir a transmissão antecipada de dicas em conexões HTTP/2 e HTTP/3. Esse estágio ainda está em fase inicial, mas se você gerencia ativamente suas próprias configurações de TLS e conexão, esse é o momento ideal para contribuir.

Estratégia: suporte legado

Talvez você tenha um código legado ou de terceiros no seu site que dependa de navigator.userAgent, incluindo partes da string do user agent que serão reduzidas. A longo prazo, é recomendável mudar para as chamadas navigator.userAgentData equivalentes, mas há uma solução provisória.

A retrofill do UA-CH é uma pequena biblioteca que permite substituir navigator.userAgent por uma nova string criada com base nos valores navigator.userAgentData solicitados.

Por exemplo, este código vai gerar uma string de user agent que também inclui a dica "modelo":

import { overrideUserAgentUsingClientHints } from './uach-retrofill.js';
overrideUserAgentUsingClientHints(['model'])
  .then(() => { console.log(navigator.userAgent); });

A string resultante mostraria o modelo Pixel 5, mas ainda mostraria o 92.0.0.0 reduzido, já que a dica uaFullVersion não foi solicitada:

Mozilla/5.0 (Linux; Android 10.0; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.0.0 Mobile Safari/537.36

Suporte adicional

Se essas estratégias não abordarem seu caso de uso, inicie uma Discussão em privacy-sandbox-dev-support repo para analisarmos o problema juntos.

Foto de Ricardo Rocha no Unsplash (links em inglês)