# Device Lifecycle

:::note[In one sentence]

A user's first successful login binds their phone to their account. From then on
only that phone can complete their location factor, until you replace it or
revoke it.

:::

Binding changes what LFA establishes from "a phone in the region" to "this
user's phone in the region": presence and possession. Without binding, anyone
with the app inside the geofence could complete anyone's location factor. With
binding, the factor is pinned to the device the user enrolled.

Each user has one bound device per client.

---

## Enrollment: the first login

Enrollment requires no administration. During a user's first login:

1. 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
   on Android (device enrollment).
2. Your token exchange supplies
   [`octet_user_id`](/docs/lfa/getting-started/quickstart/), and LFA binds the enrolled
   device to that user. The first device for a user is bound automatically.

Every later login checks that the device is the one bound to this user. As long
as the user keeps the same phone, there is nothing further to do.

:::note[Broker clients bind to a different identifier]

The step above is the **direct** path: your OIDC client sends `octet_user_id` on
the RP-authenticated token exchange. If you integrate through an identity broker
such as Okta, the broker runs a fixed standard token exchange and cannot add
`octet_user_id`. Instead, the binding identifier is the `login_hint` carried
inside the broker's signed authorization request. LFA
verifies that request against the broker's pinned keys, so the device binds to
the authenticated hint, never a raw front-channel value. See
[Okta Integration](/docs/lfa/integration/okta/) for setup. The rest of this page applies
to both, with the device-replacement difference called out below.

:::

## Replacing a phone

If the user still controls their account, let them replace the device
themselves. After **your own re-verification step** (a step-up you trust), send
one extra field on the token exchange:

```bash
octet_device_enroll=true
```

LFA revokes the old device and binds the new one, atomically. Without the flag, a
new device claiming an already-bound user is rejected (`invalid_grant`). That
rejection is what blocks an account-takeover attempt from swapping in an
attacker's phone, so gate the flag behind real verification, not a self-serve
checkbox.

:::caution[Broker clients cannot replace a device this way]

`octet_device_enroll` is sent on the direct token exchange, and brokers cannot
add fields to it, so enrollment is forced off for broker clients. When a broker user
gets a new phone, their next login is rejected (`invalid_grant`) and stays
rejected until you clear the old binding with
[`POST /v1/device/revoke`](/docs/lfa/reference/oidc/#post-v1devicerevoke) (below). After
the revoke, the next login binds the new phone automatically, with no flag and
no further action.

:::

## A lost or stolen phone

Revoke the binding server-side, immediately. No login flow is required. A direct
client calls the endpoint itself with its client secret:

```bash
curl -s https://factor.octetproof.com/v1/device/revoke \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d octet_user_id=usr_1234
```

A successful call returns `{"revoked": true, "device_id": "<id>"}`. If no active
device is bound to that user, it returns `404 no_device`. See the
[OIDC reference](/docs/lfa/reference/oidc/#post-v1devicerevoke) for every response.

From that moment the revoked phone is denied at proof submission, regardless of
where it is or what it can prove. When the user signs in on a replacement phone,
it binds automatically. A revoked device no longer counts as a live one, so no
`octet_device_enroll` flag is needed.

For a broker client, ask Octet to revoke the
binding. The endpoint accepts a client secret only, and a broker integration
holds no client secret. Revocation is also the only device-recovery path for a
broker, because brokers cannot send `octet_device_enroll`. Give Octet the
identifier the device is bound to, which is the `login_hint` value from the
signed request object. For a direct client, that identifier is your
`octet_user_id`.

## What each rejection means

| Error | Meaning | What to do |
|---|---|---|
| `invalid_request` (`octet_user_id is required`) | A direct token exchange omitted the user identifier | Send `octet_user_id` on every exchange |
| `invalid_grant` (`this device is bound to a different user`) | The phone that proved presence belongs to another account | The user must use their own enrolled phone |
| `invalid_grant` (`a new device requires enrollment`) | The user already has a bound device and this is a different one | **Direct:** re-verify the user, then retry with `octet_device_enroll=true`. **Broker:** ask Octet to revoke the old binding (`POST /v1/device/revoke`). The next login then binds the new phone |

## Scoping

Each client keeps its own bindings, and the user identifiers you send are never
visible to other clients. See
[Trust & Privacy](/docs/lfa/concepts/trust-and-privacy/).

## Where to go next

- [Quick Start](/docs/lfa/getting-started/quickstart/). Where `octet_user_id` goes.
- [OIDC Reference](/docs/lfa/reference/oidc/#post-v1devicerevoke). The revoke endpoint's
  full contract.
