# 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 is what upgrades LFA from "a phone in the region" to "**this user's
phone** in the region": presence and possession. Without it, anyone with the app
inside the geofence could complete anyone's location factor. With it, the factor
is pinned to the device the user enrolled.

The current model is deliberately simple: **one bound device per user, per
client.**

---

## Enrollment: the first login

Nothing to administer. During a user's first login:

1. The app creates a signing key in the phone's secure hardware and has Apple
   App Attest certify it to LFA (device enrollment).
2. Your token exchange supplies
   [`octet_user_id`](/docs/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 one thing: is this the device bound to this user? The
same phone means nothing to do, ever.

:::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
(Okta, Microsoft Entra ID, Auth0), the broker runs a fixed standard token
exchange and **cannot** add `octet_user_id`. Instead, the bind user 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/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, and
the moment an account-takeover attempt would strike), 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 the protection, so gate the flag behind real verification, not a
self-serve checkbox.

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

`octet_device_enroll` rides 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/reference/oidc/) (below). After the revoke, the next
login TOFU-binds the new phone automatically. No flag, no further action.

:::

## A lost or stolen phone

Revoke the binding server-side, immediately. No login flow is required:

```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
```

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.

This endpoint works for **both direct and broker clients**, and it is the
**only** device-recovery path for a broker. Since brokers cannot send
`octet_device_enroll`, an admin revoke is how a broker user moves to a new phone.
The identifier you pass is the same one the device is bound to: your
`octet_user_id` for a direct client, or the signed-request `login_hint` for a
broker client.

## 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:** revoke the old binding with `POST /v1/device/revoke`, then the next login binds the new phone |

## Scoping

Bindings are **per client**. A phone used with two of your clients (or with two
different Octet customers) has independent bindings, and the user identifiers you
send are never visible across clients. See
[Trust & Privacy](/docs/concepts/trust-and-privacy/).

## Where to go next

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