# How a Login Works

:::note[In one sentence]

LFA is an OIDC identity provider that issues tokens only after the user's phone
produces a hardware-attested proof of presence in the region your policy names.

:::

## The moving parts

- **Your application** (or your broker) starts the login and consumes the
  tokens.
- **The browser** shows a QR code and waits.
- **The Octet Location app** runs the location check on the phone and produces
  the proof. It embeds the [Octet SDK](/docs/), which is what measures and signs
  [proof of location](/docs/concepts/proof-of-location/).
- **LFA** (`factor.octetproof.com`) checks the proof
  ([how verification works](/docs/concepts/verifying-proofs/)) against your
  [policy](/docs/concepts/policies/), then answers over standard OIDC.

## The flow

```mermaid
flowchart TD
    A[Your app redirects to /authorize] --> B[Browser shows QR code / app link]
    B --> C[User scans with the Octet Location app]
    C --> D[App runs the location check<br/>signed proof, region verdict only]
    D --> E[Phone submits proof to LFA<br/>with a hardware attestation]
    E --> F{LFA verifies:<br/>attestation, freshness, policy}
    F -->|pass| G[Browser returns to your app with a code]
    F -->|fail| H[access_denied]
    G --> I[Your app exchanges the code + octet_user_id]
    I --> J[id_token with acr urn:octet:loc:strong<br/>+ signed location assertion]
```

Step by step:

1. Your app redirects to `/authorize` exactly as it would for any external OIDC
   provider (code flow, PKCE, `state`, `nonce`).
2. The browser lands on the LFA login page (a QR code, or a tap-to-open app link
   on the phone itself) and waits on a live status stream.
3. The user scans the code. The app fetches the session's parameters, including
   a **server-issued one-time challenge** the proof must commit to.
4. The app runs the Octet location check on-device: sensors, motion, and the
   anti-spoof pipeline produce a signed proof whose payload is a **region
   verdict**, never coordinates.
5. The phone submits the proof with a hardware attestation. LFA verifies the
   whole chain fail-closed (signature, device identity, Apple App Attest,
   freshness, and the [policy](/docs/concepts/policies/)) and pushes the result to the
   waiting browser.
6. The browser returns to your app with an authorization code. You exchange it
   (with [`octet_user_id`](/docs/getting-started/quickstart/)) for the tokens.

## Direct integrations and brokers

The flow above shows a **direct** integration: your app authenticates the
`/token` exchange with its client secret and sends `octet_user_id` there. That
is the identifier LFA binds the attested device to (see
[Device lifecycle](/docs/concepts/device-lifecycle/)).

Identity brokers (Okta, Microsoft Entra ID, Auth0) run a fixed, standard token
exchange and cannot add `octet_user_id`, so they bind over the front channel
instead. A broker authenticates the `/authorize` request with a **signed request
object** ([RFC 9101](https://www.rfc-editor.org/rfc/rfc9101), a JAR passed as
`request=<JWS>`). LFA verifies that object against the broker's pinned signing
keys and binds the device to the `login_hint` carried **inside** the verified
object.

In both cases the user id LFA binds to arrives over an authenticated channel: a
client-secret-authenticated `/token` exchange, or a signature-verified
`/authorize` object. It is never read from the raw browser redirect.
Front-channel tampering (a browser rewriting `login_hint` to a victim's id)
breaks the signature, and the request is rejected.

:::note

Direct clients are unaffected by broker handling. A stray `request=` parameter
on a non-broker client is ignored, and direct clients keep using
`octet_user_id` and `octet_device_enroll` on the RP-authenticated `/token`.

:::

See [Okta](/docs/integration/okta/) for the broker setup, and
[Device lifecycle](/docs/concepts/device-lifecycle/) for how binding and device
replacement work in each mode.

## First login versus later logins

The **first time** a phone is used, two one-time things happen inside the same
flow. The user sees a slightly longer first login:

- The app creates a signing key in the phone's secure hardware and has the
  platform certify it to LFA: Apple App Attest on iOS, hardware key attestation
  chained to a Google root on Android. This is **device enrollment**.
- Your token exchange **binds** that device to the `octet_user_id` you sent (see
  [Device lifecycle](/docs/concepts/device-lifecycle/)).

Every later login reuses the enrolled key. The phone produces a fresh hardware
signature over this session's challenge, and LFA checks it against the key and
binding on file. No re-enrollment, no extra prompts.

### Push for enrolled devices

Once a device is enrolled and bound to a user, LFA can reach that phone directly.
If your `/authorize` request identifies the user (a broker's signed request
object carries them in `login_hint`), LFA sends a **push notification** to their
bound phone instead of expecting a scan:

> **Location check requested** -- *Acme is requesting a location check. Tap to
> review.*

The user taps the notification, sees the same consent screen, and approves. No
camera, no scan. The pushed session lasts 180 seconds, and a short per-user
cooldown stops repeated requests piling up on someone's lock screen.

The QR code remains the fallback: when the request does not identify the user,
the device is not enrolled yet, or the push does not arrive. Your integration is
the same code either way. Only the way the phone hears about the session differs.

## Why a captured proof cannot be reused

Everything in the flow is single-use and short-lived:

- The proof commits to a **per-session server challenge**, signed inside the
  proof itself, so it cannot be replayed into any other login. The guarantee
  holds wherever the proof travels.
- The login session lives **90 seconds**, the authorization code **60 seconds**
  and one redemption, the location assertion **120 seconds**.
- Proof freshness is bounded by the policy's
  [`max_proof_age_s`](/docs/concepts/policies/#proof-freshness).

A proof is a statement about now. Nothing in the flow doubles as a durable
credential.

## Where to go next

- [Policies](/docs/concepts/policies/). What "pass" means: region, attestation floor,
  freshness, spoof-verdict floor.
- [Device lifecycle](/docs/concepts/device-lifecycle/). Enrollment, binding, new
  phones, lost phones.
