# 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.

:::

## Components

- **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/mobile/), which is what measures and signs
  [proof of location](/docs/mobile/concepts/proof-of-location/).
- **LFA** (`factor.octetproof.com`) checks the proof
  ([how verification works](/docs/mobile/concepts/verifying-proofs/)) against your
  [policy](/docs/lfa/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 the phone and produces 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, platform attestation,
   freshness, and the [policy](/docs/lfa/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/lfa/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/lfa/concepts/device-lifecycle/)).

Identity brokers such as Okta 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/lfa/integration/okta/) for the broker setup, and
[Device lifecycle](/docs/lfa/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 steps happen inside the same flow,
so the first login takes slightly longer:

- 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/lfa/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. There is no re-enrollment and no extra prompt.

### 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 in a verified way (a broker
client'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,
without scanning a QR code. A broker login 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
(including every direct-client login, whose `login_hint` is unverified), the
device is not enrolled yet, or the push does not arrive. Your integration
code is the same either way. Only the delivery of the session to the phone
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, or 180 seconds for a broker login that
  can be pushed to a bound phone. The authorization code lives 60 seconds and
  can be redeemed once. The location assertion lives 120 seconds.
- The proof must be fresh when LFA checks it (see
  [Policies](/docs/lfa/concepts/policies/#attestation-and-freshness)).

Nothing in the flow can serve as a long-lived credential.

## Where to go next

- [Policies](/docs/lfa/concepts/policies/). What "pass" means: country, attestation,
  freshness, and anti-spoof tier.
- [Device lifecycle](/docs/lfa/concepts/device-lifecycle/). Enrollment, binding, new
  phones, lost phones.
