# Trust & Privacy

:::note[In one sentence]

The phone proves which region it is in without revealing where in that region it
is, and LFA stores the minimum needed to keep the factor bound to the right
device and user.

:::

## What never leaves the phone

The location check runs entirely on the phone. What leaves the phone is a signed
proof whose payload is a region verdict ("inside GB") plus what is needed to
verify it. Coordinates and raw readings never leave the device. LFA never receives them, so it cannot
disclose them.

## Why a verdict can be trusted

The verdict carries no coordinates, so it is only as trustworthy as the chain
that produces it. LFA verifies that chain fail-closed on every login:

1. **The proof is signed on-device**, produced by the Octet SDK's location and
   anti-spoof pipeline (the same proof-of-location machinery as the
   [Octet Mobile SDK](/docs/mobile/concepts/proof-of-location/)).
2. **The device is hardware-attested.** The signing key lives in the phone's
   secure hardware. At [enrollment](/docs/lfa/concepts/device-lifecycle/) the platform
   certifies, once, that the genuine Octet Location app created it on a genuine
   device: Apple App Attest on iOS, hardware key attestation on Android. LFA
   verifies the evidence against the platform's root certificates directly
   (Apple's for App Attest, Google's for Android key attestation).
3. **Every login is freshly signed, and the freshness is committed inside the
   proof.** The phone commits this session's one-time challenge inside the signed
   proof, identically on both platforms. Freshness therefore travels with the
   proof rather than alongside it: a captured proof cannot be replayed into
   another login even if it is stored, forwarded, or verified later and out of
   band.
4. **Any failed check is a denial.** Weak attestation, a stale proof, a spoof
   tier the [policy](/docs/lfa/concepts/policies/#anti-spoof-tier) does not accept, an
   unknown or revoked device: LFA denies rather than scoring the result.

## What LFA stores

| Stored | What it is | Why |
|---|---|---|
| Device key + attestation record | The device's public key and its platform attestation (App Attest on iOS, key attestation on Android) | To verify each login's hardware signature |
| Push token and device details | The phone's push token, platform, an optional display name, enrollment time, and last successful login time | To send a sign-in request to a bound phone |
| Device-to-user binding | The per-client user identifier you supply (`octet_user_id` for a **direct** client, or the `login_hint` from the signed request object for a **broker** client) | To pin the factor to the user's own phone |
| Revocation flag | Whether a device has been revoked | To deny lost or replaced phones |
| Usage counts | Daily sign-in counts per client, by outcome. No user ID, device, or location | To count usage for billing |

On a direct integration you choose the binding identifier, and LFA never sees a
password, email, or profile unless you send one as the ID (send an opaque
internal ID instead, see [Prerequisites](/docs/lfa/getting-started/prerequisites/)). On a
broker path LFA stores the username your broker signs, which is often an email
address.
Bindings are scoped per client, so identifiers are never visible across clients
and LFA builds no cross-application identity graph.

The binding identifier reaches LFA one of two ways:

- **Direct clients** send `octet_user_id` on the RP-authenticated `/token` call.
  This is the standard path.
- **Broker clients** (such as Okta) run a fixed token
  exchange and cannot add `octet_user_id`, so they instead authenticate the
  front channel. Their `/authorize` request carries a signed request object, and
  the `login_hint` inside that verified object becomes the binding
  identifier. LFA never binds to the raw query-string `login_hint`. See
  [Okta integration](/docs/lfa/integration/okta/) and the
  [OIDC reference](/docs/lfa/reference/oidc/).

:::note

Either way, what LFA stores is a user identifier, with no coordinates,
credentials, or profile. On a direct integration we recommend an opaque internal
ID.

:::

## What LFA does not store

- **Coordinates or location history.** Never received in the first place.
- **User profiles or credentials.** Identity stays with your IdP.
- **`login_hint` (direct clients).** Passed through into the id_token as
  `octet_login_hint`, not persisted. For broker clients this is different:
  the `login_hint` from the signed request object is the binding identifier and
  is stored, exactly as `octet_user_id` is for a direct client (see
  [What LFA stores](#what-lfa-stores)). It is still only an identifier, with
  no coordinates, credentials, or profile.
- **Long-lived tokens.** The login session lives 90 seconds (180 seconds for a
  broker login), the authorization
  code 60 seconds, the location assertion 120 seconds. There are no refresh
  tokens.

## Where to go next

- [Device lifecycle](/docs/lfa/concepts/device-lifecycle/). Enrollment, replacement, and
  revocation of the bound device.
- [How a login works](/docs/lfa/concepts/how-a-login-works/). The full flow the chain
  above protects.
