Pular para o conteúdo
No ar em produção, em modo shadow: o FingerTrackID recomenda, o seu sistema decide.Status →

Login com Google, OAuth e outros redirecionamentos

Um login com senha fica na sua página até o envio, então a receita de coleta cobre esse caso. Um login com o Google ou com outro provedor OAuth/OIDC normalmente não fica: o navegador sai da sua página para o provedor e volta para um callback no seu servidor. Esta página mostra como preservar a evidência do navegador nesse fluxo e como verificá-la.

O SDK mantém a sessão, as observações, os handles de evento e o token apenas na memória da página. Ele nunca os grava em armazenamento, e não deve gravar: o token é uma credencial de uso único para um evento de negócio. Uma navegação de página inteira descarta tudo isso. Se a página inicia o SDK e depois navega para o provedor:

  • a primeira requisição do SDK pode ser interrompida pela navegação, e assim nenhuma coleta chega ao FingerTrackID;
  • mesmo uma coleta concluída fica inútil depois que o handle dela se perde, porque só aquela página poderia ter pedido o contexto do evento;
  • o seu callback roda no servidor, onde não há SDK, então o login é avaliado com event_context_token: null.

Nada falha de forma visível: o login funciona e a avaliação é armazenada. O que falta é a evidência do navegador.

Seu backend ainda envia a avaliação de login (etiqueta login; LOGIN na v1) com event_context_token: null, que o schema da requisição aceita como “nenhuma coleta fornecida”. A avaliação armazenada fica então com:

Campo Sem evidência do navegador
assessment_status INCONCLUSIVE
trust_score null (sem nota; não é 0, não é 100)
device_intelligence_snapshot_id, device_intelligence_snapshot_version, sdk_version, device_id null
signals Todos os 13 indicadores NOT_EVALUATED
limitation_codes Por exemplo, COLLECTOR_NOT_LOADED
evidence_coverage 0

device_intelligence_snapshot_id: null é a forma confiável de saber, só pela resposta, que nenhuma evidência do navegador foi anexada.

Use as receitas da página de coleta, para um POST de formulário clássico ou uma aplicação de página única. O contexto vai com a requisição que o seu backend avalia: o formulário de senha, ou o formulário do código de MFA quando o seu login pede um. O backend avalia depois de verificar a senha e o MFA, se houver, usando o token só enquanto o expiresAt dele deixa tempo suficiente (wrapper fail-open).

Os dois padrões produzem um contexto para o login e o levam até o seu backend sem nunca colocá-lo numa URL, no state do OAuth ou no armazenamento do navegador. Escolha pelo tempo que os usuários passam no provedor em comparação com a janela de evidência da sua aplicação:

Padrão Coleta Melhor quando Custo
A. Antes do redirecionamento Na primeira intenção em direção a “Continuar com o Google”, com o contexto obtido no clique nele A ida e volta até o provedor cabe com folga dentro da janela Até o seu teto antes do redirecionamento
B. Depois do callback Numa página intermediária sua, antes de a sessão ser concluída Os usuários podem passar muito tempo no provedor (escolha de conta, senha, 2FA do provedor), ou a janela é de 60 segundos Uma página a mais, que espera até o seu teto

Com uma janela de 60 segundos, o tempo gasto no provedor corre contra o token, então o padrão B é a escolha mais confiável. Com 300 segundos, o padrão A cobre a maioria das idas e voltas ao provedor. Logins por popup, One Tap e FedCM não saem da página, e o usuário pode nunca tocar num botão seu: chame prepareNextAction() no primeiro focusin ou pointerdown do formulário de login ou do contêiner que abriga o login, no pointerenter, focus ou pointerdown do botão de login, ou quando o seu código exibe o prompt do One Tap ou do FedCM depois de uma ação do usuário. Nunca no carregamento da página: não prepare quando o One Tap ou o FedCM se exibe sozinho enquanto a página carrega, porque um prompt exibido a cada carregamento abriria, a cada carregamento, uma coleta que conta como uso (a unidade de uso), também para visitantes que nunca fazem login. Obtenha o contexto com takeActionContext() quando a credencial chegar e envie-o com a requisição que faz o POST da credencial, como num login com senha. Sem uma preparação antes, o callback da credencial espera até o seu teto enquanto a coleta começa, e muitas vezes chega ao seu backend com null.

Padrão A: obtenha o contexto no clique, antes de redirecionar

Seção intitulada “Padrão A: obtenha o contexto no clique, antes de redirecionar”
  1. Chame prepareNextAction() da receita de coleta na primeira intenção em direção ao botão do Google (pointerenter, focus, pointerdown), nunca no carregamento da página, para que o contexto possa estar pronto até o clique. No clique, obtenha-o com takeActionContext(), que espera no máximo o seu teto. Em dispositivos de toque, o pointerdown vem logo antes do clique, então o clique pode esperar até SUBMIT_WAIT_MS.
  2. Envie o contexto (ou null com o motivo) ao seu backend numa requisição de mesma origem, protegida contra CSRF. O backend o guarda no servidor, na sessão pré-login que já guarda o state do OAuth e o verificador PKCE (ou o cookie criptografado, HttpOnly e de curta duração que o seu framework usa para eles), vinculado a esse state.
  3. Inicie o redirecionamento para o provedor como você já faz hoje. Não chame close() antes: fechar pode invalidar o contexto ainda não consumido.
  4. No callback, depois de verificar a resposta do provedor e o seu próprio MFA, se houver, pegue o contexto armazenado e apague-o (uso único). Envie o token se o expiresAt dele ainda deixa a sua margem; caso contrário, envie null e registre EXPIRED_BEFORE_CALLBACK no log.

Se a sua biblioteca de autenticação inicia o fluxo do provedor a partir do navegador (uma chamada no estilo signIn('google')), mantenha essa chamada como último passo e envie o contexto numa requisição própria logo antes dela, como acima. A página é descartada no redirecionamento sem close(); a sessão do SDK dela ocupa uma das suas vagas de sessões simultâneas até que os 30 minutos dela se esgotem.

// prepareNextAction and takeActionContext come from the collection recipe; csrfToken and
// startGoogleSignIn stand for your existing CSRF token and provider redirect.
// The first intent prepares the context; a page load alone opens no session and bills nothing.
for (const intent of ['pointerenter', 'focus', 'pointerdown'])
googleButton.addEventListener(intent, () => prepareNextAction())
googleButton.addEventListener('click', async (event) => {
event.preventDefault()
googleButton.disabled = true // Show progress; the wait is bounded by SUBMIT_WAIT_MS.
const { context, failure } = await takeActionContext()
try {
// Your endpoint: stores { context, failure } server-side with the OAuth state.
await fetch('/auth/login-context', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },
body: JSON.stringify({ eventContext: context, contextFailure: failure }),
})
} catch {
// The login must not depend on this request; the callback then assesses with null.
}
startGoogleSignIn() // Your existing redirect to the provider, unchanged.
})
  1. O callback verifica a resposta do provedor, mas não conclui a sessão ainda. Ele armazena no servidor um login pendente de curta duração (vinculado ao navegador, por exemplo pela mesma sessão pré-login) e redireciona para uma página intermediária sua, como /login/continue, numa das origens permitidas da aplicação.
  2. Essa página coleta e obtém o contexto com takeActionContext(), que espera no máximo o seu teto, e então o envia por POST (ou null com o motivo) ao seu backend.
  3. O backend verifica o login pendente, executa o seu próprio MFA, se houver, avalia o login, depois conclui a sessão como já faz hoje e responde com o próximo destino.
  4. A página chama close() depois dessa resposta e segue a navegação.

A página intermediária precisa sempre concluir o login: mantenha a espera limitada e dê a ela um formulário <noscript> (ou um botão “Continuar” simples) que envia por POST sem contexto. Se o usuário nunca chega a /login/finish, o login pendente expira como qualquer login abandonado. Quando a sua aplicação pede o próprio MFA depois do provedor, essa página de MFA já é a página depois do callback: use a receita de login com senha no formulário dela e dispense a página extra.

// On /login/continue. takeActionContext and closeSdk come from the collection recipe.
async function finishLogin() {
const { context, failure } = await takeActionContext()
const response = await fetch('/login/finish', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },
body: JSON.stringify({ eventContext: context, contextFailure: failure }),
})
const { next } = await response.json() // Your own response shape.
await closeSdk() // The backend has answered: free the session slot (at most 1500 ms).
window.location.assign(next)
}
finishLogin()
  • Envie exatamente uma avaliação de login (etiqueta login; LOGIN na v1) para cada login concluído, com senha ou por provedor, com os mesmos pseudônimos subject_ref e account_ref para a mesma pessoa e a mesma conta. Nunca envie o identificador de sujeito do provedor nem o e-mail. Uma conta fica vinculada ao subject_ref com que foi enviada pela primeira vez: um login por provedor que associa a mesma conta a outra pessoa retorna 409 binding-conflict (referências de pessoa e de conta).
  • Aceite o contexto só da requisição que a sua própria página enviou para este login; apague um contexto armazenado depois do primeiro uso e nunca o use para outro login.
  • Envie o token só enquanto o expiresAt deixa a sua margem; caso contrário, envie null. Em caso de 400 event-context-invalid, avalie mais uma vez, como uma nova operação, com null (wrapper fail-open).
  • Registre em log, para cada avaliação, se ela levou um token e, quando não levou, o contextFailure do navegador ou o seu próprio motivo (NO_CONTEXT_RECEIVED, EXPIRED_BEFORE_CALLBACK). Nunca registre o token em log.
  • O login em si nunca espera pelo FingerTrackID além do seu próprio orçamento, e nunca falha por causa dele.

Faça estas verificações em cada origem permitida que você usa, para um login com senha e para cada provedor, antes de ligar a integração para todos:

  1. Painel de rede do navegador. As requisições ao seu domínio de coleta terminam com uma resposta antes de a página sair. Uma requisição mostrada como cancelada logo antes de um redirecionamento significa que a coleta rodou tarde demais ou que o redirecionamento não esperou. Cada requisição OPTIONS ao domínio de coleta é respondida com 204; um 400 ali significa que uma instrumentação da página adicionou um cabeçalho à requisição (cabeçalhos de rastreamento).
  2. Logs do seu backend. A maioria dos logins leva um token. Os logins sem token têm um motivo, e os motivos são esperados (por exemplo, WAIT_EXCEEDED em redes lentas), não um mesmo código fixo em todo login por provedor.
  3. A resposta da avaliação. Com evidência do navegador, device_intelligence_snapshot_id e sdk_version não são null, BOT_AUTOMATION é DETECTED ou NOT_DETECTED, evidence_coverage fica acima de 0 e limitation_codes não contém COLLECTOR_NOT_LOADED. data_freshness.maximum_required_age_ms mostra a janela de evidência da sua aplicação.
  4. Console, Integração e saúde. Última coleta mostra um horário recente em vez de nenhuma coleta na janela; em Da coleta à avaliação, Coletas aceitas cresce com os seus logins e Correlacionadas (avaliações que encontraram a coleta do navegador delas) fica perto de Avaliações; Correlação navegador → backend mostra essa proporção. Esses números cobrem as últimas 24 horas registradas e não são em tempo real.
  5. Console, Acessos. Abra um login por provedor: Cobertura de evidência fica acima de zero, o indicador Automação (robô) mostra Detectado ou Não detectado em vez de Não avaliado, e as limitações não incluem Coletor não carregado.

Se todo login por provedor falha nestas verificações enquanto os logins com senha passam, o redirecionamento está descartando o contexto: aplique o padrão A ou o B. Veja também o checklist de integração.