Login with Google, OAuth and other redirects
A password login stays on your page until its submit, so the collection recipe covers it. A login through Google sign-in or another OAuth/OIDC provider usually does not: the browser leaves your page for the provider and comes back to a callback on your server. This page shows how to keep browser evidence in that flow and how to verify it.
Why a redirect loses the context
Section titled “Why a redirect loses the context”The SDK keeps its session, observations, event handles and the token only in page memory. It never writes them to storage, and it must not: the token is a single-use credential for one business event. A full-page navigation discards all of it. If the page starts the SDK and then navigates to the provider:
- the SDK’s first request may be cut off by the navigation, so no collection reaches FingerTrackID;
- even a completed collection is useless once its handle is gone, because only that page could have asked for the event context;
- your callback runs on the server, where there is no SDK, so the login is assessed with
event_context_token: null.
Nothing fails visibly: the login works and the assessment is stored. What is missing is the browser evidence.
What happens without a context
Section titled “What happens without a context”Your backend still sends the login assessment (tag login; LOGIN in v1) with
event_context_token: null, which the
request schema
accepts as “no collection supplied”. The stored assessment then has:
| Field | Without browser evidence |
|---|---|
assessment_status |
INCONCLUSIVE |
trust_score |
null (no score; not 0, not 100) |
device_intelligence_snapshot_id, device_intelligence_snapshot_version, sdk_version, device_id |
null |
signals |
All 13 indicators NOT_EVALUATED |
limitation_codes |
For example COLLECTOR_NOT_LOADED |
evidence_coverage |
0 |
device_intelligence_snapshot_id: null is the reliable way to tell, from the response alone,
that no browser evidence was attached.
Password login
Section titled “Password login”Use the recipes on the collection page, for a
classic form POST or a
single-page application. The context goes
with the request your backend assesses: the password form, or the MFA code form when your login
asks for one. The backend assesses after it has verified the password and any MFA, using the
token only while its expiresAt leaves enough time
(fail-open wrapper).
Redirect logins: two patterns
Section titled “Redirect logins: two patterns”Both patterns produce one context for the login and carry it to your backend without ever
putting it in a URL, the OAuth state or browser storage. Pick by how long users spend at the
provider compared with your application’s
evidence window:
| Pattern | Collects | Best when | Cost |
|---|---|---|---|
| A. Before the redirect | On the first intent toward “Continue with Google”, taken on its click | The round trip to the provider fits comfortably inside the window | Up to your ceiling before the redirect |
| B. After the callback | On an intermediate page of yours, before the session is finished | Users may spend long at the provider (account choice, password, provider 2FA), or the window is 60 seconds | One extra page, which waits up to your ceiling |
With a 60-second window, time spent at the provider counts against the token, so pattern B
is the more reliable choice. With 300 seconds, pattern A covers most provider round trips.
Popup, One Tap and FedCM sign-ins do not leave the page, and the user may never touch a button of
yours: call prepareNextAction() on the first focusin or pointerdown of the login form or of
the container that holds the sign-in, on the sign-in button’s pointerenter, focus or
pointerdown, or when your code displays the One Tap or FedCM prompt after a user action. Never
at page load: do not prepare when One Tap or FedCM displays itself automatically as the page
loads, because a prompt displayed on every load would open a collection that counts as usage (the
usage unit) on every load, also for visitors who never sign in. Take the context with
takeActionContext() when the credential arrives and send it with the request that posts the
credential, as in a password login. Without a prepare beforehand, the credential callback
waits up to your ceiling while the collection starts, and often reaches your backend with
null.
Pattern A: take the context on the click, before redirecting
Section titled “Pattern A: take the context on the click, before redirecting”- Call
prepareNextAction()from the collection recipe on the first intent toward the Google button (pointerenter,focus,pointerdown), never at page load, so the context can be ready by the click. On the click, take it withtakeActionContext(), which waits at most your ceiling. On touch devicespointerdowncomes just before the click, so the click may wait up toSUBMIT_WAIT_MS. - Send the context (or
nullwith its reason) to your backend in a same-origin, CSRF-protected request. The backend keeps it server-side, in the pre-login session that already holds your OAuthstateand PKCE verifier (or the encrypted,HttpOnly, short-lived cookie your framework uses for them), bound to thatstate. - Start the provider redirect as you do today. Do not call
close()first: closing can invalidate the unconsumed context. - In the callback, after verifying the provider’s response and any MFA of your own, take the
stored context and delete it (single use). Send the token if its
expiresAtstill leaves your margin; otherwise sendnulland logEXPIRED_BEFORE_CALLBACK.
If your authentication library starts the provider flow from the browser (a signIn('google')
style call), keep that call as the last step and send the context in its own request just
before it, as above. The page is discarded at the redirect without close(); its SDK session
keeps one of your concurrent-session slots until its
30 minutes run out.
// 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.})Pattern B: collect on a page after the callback
Section titled “Pattern B: collect on a page after the callback”- The callback verifies the provider’s response but does not finish the session yet. It
stores a short-lived pending login server-side (bound to the browser, for example with the
same pre-login session) and redirects to an intermediate page of yours, such as
/login/continue, on one of the application’s allowed origins. - That page collects and obtains the context with
takeActionContext(), which waits at most your ceiling, then posts it (ornullwith its reason) to your backend. - The backend verifies the pending login, runs any MFA of your own, assesses the login, then finishes the session as it does today and answers with where to go next.
- The page calls
close()after that answer and navigates on.
The intermediate page must always finish the login: keep the bounded wait, and give it a
<noscript> form (or a plain “Continue” button) that posts without a context. If the user never
reaches /login/finish, the pending login expires like any abandoned sign-in. When your
application asks its own MFA after the provider, that MFA page already is the page after the
callback: use the password-login recipe on its form and
skip the extra page.
// 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()Backend rules for both patterns
Section titled “Backend rules for both patterns”- Send exactly one login assessment (tag
login;LOGINin v1) for every completed login, password or provider, with the samesubject_refandaccount_refpseudonyms for the same person and account. Never send the provider’s subject identifier or the email. An account stays bound to thesubject_refit was first sent with: a provider login that maps the same account to another person returns409 binding-conflict(person and account references). - Accept the context only from the request your own page sent for this login; delete a stored context after its first use, and never use it for another login.
- Send the token only while
expiresAtleaves your margin; otherwise sendnull. On400 event-context-invalid, assess once more as a new operation withnull(fail-open wrapper). - Log, for every assessment, whether it carried a token and, when not, the browser’s
contextFailureor your own reason (NO_CONTEXT_RECEIVED,EXPIRED_BEFORE_CALLBACK). Never log the token. - The login itself never waits on FingerTrackID beyond your own budget, and never fails because of it.
Check that SDK evidence arrives
Section titled “Check that SDK evidence arrives”Run these checks on each allowed origin you use, for a password login and for each provider, before turning the integration on for everyone:
- Browser network panel. Requests to your collection domain complete with a response before
the page navigates away. A request shown as cancelled right before a redirect means the
collection ran too late or the redirect did not wait. Each
OPTIONSrequest to the collection domain is answered with204; a400there means page instrumentation added a request header (tracing headers). - Your backend logs. Most logins carry a token. Logins without one have a reason, and the
reasons are expected ones (for example
WAIT_EXCEEDEDon slow networks), not one fixed code on every provider login. - The assessment response. With browser evidence,
device_intelligence_snapshot_idandsdk_versionare notnull,BOT_AUTOMATIONisDETECTEDorNOT_DETECTED,evidence_coverageis above0andlimitation_codesdoes not containCOLLECTOR_NOT_LOADED.data_freshness.maximum_required_age_msshows your application’s evidence window. - Console, Integração e saúde. Última coleta shows a recent time instead of nenhuma coleta na janela; in Da coleta à avaliação, Coletas aceitas grows with your logins and Correlacionadas (assessments that found their browser collection) stays close to Avaliações; Correlação navegador → backend shows that share. These numbers cover the last 24 recorded hours and are not live.
- Console, Acessos. Open a provider login: Cobertura de evidência is above zero, the Automação (robô) indicator shows Detectado or Não detectado instead of Não avaliado, and the limitations do not include Coletor não carregado.
If every provider login fails these checks while password logins pass, the redirect is discarding the context: apply pattern A or B. See also the integration checklist.