# FAQ & Troubleshooting

Indexed by what you see. Errors at the token endpoint follow standard OAuth
shapes (`error` plus `error_description`).

Check the [service status page](https://api.octetproof.com/status) to see whether an Octet service is having problems.

---

## At the login page

| Symptom | Cause and fix |
|---|---|
| QR page expires before the user finishes | The login session lives 90 seconds, or 180 seconds for a broker login. Restart the login, and have the phone ready |
| Phone shows "Check complete" but the login fails | The phone shows the same screen whether or not the login passed. Your app gets the result: the browser returns with `error=access_denied`. Ask the user to confirm they are in the required region and retry |
| Phone shows "Update required" | The app is too old to approve a sign-in. Update Octet Location from the App Store or Google Play, then scan again |
| Browser returns `error=access_denied` | Any failed verification or policy check, including a revoked device. LFA denies when any check fails, so this does not indicate an outage |

## At `/authorize`

| Symptom | Cause and fix |
|---|---|
| `invalid_request: PKCE S256 code_challenge is required` | Your library is not sending PKCE, or sends `plain`. Enable S256 |
| `invalid_request: missing required parameter` | `state` or `nonce` is missing. Both are required, which is stricter than the OIDC spec. Configure your library to send both |
| Unknown `octet_policy_id` rejected | Deliberate, because a typo must not fall back to a different policy. Check the id against your registered policies |

## At `/token`

| Symptom | Cause and fix |
|---|---|
| `invalid_request: octet_user_id is required` | Every direct-client exchange must name the signing-in user. See the [Quick Start](/docs/lfa/getting-started/quickstart/#3-exchange-the-code-with-octet_user_id) |
| `invalid_grant: this device is bound to a different user` | The phone that proved presence is enrolled to another account. The user must use their own phone |
| `invalid_grant: a new device requires enrollment` | The user already has a bound device and just used a different one. Re-verify the user, then retry with `octet_device_enroll=true`. See [Device lifecycle](/docs/lfa/concepts/device-lifecycle/) |
| `invalid_grant: code expired / already redeemed` | Codes are single-use with a 60-second window. Exchange immediately in the callback, and check for duplicate callback invocations |

## Common questions

**Does the user have to enroll before the first login?**
No. The first successful login enrolls the phone and binds it to the
`octet_user_id` you send. It is one flow, slightly longer the first time.

**How does a user switch phones?**
Two paths. If the user still controls their account, use your own step-up
verification plus `octet_device_enroll=true`. If the phone is lost or stolen, revoke the binding with
[`/v1/device/revoke`](/docs/lfa/reference/oidc/#post-v1devicerevoke) and the next phone
binds automatically. Direct clients call it themselves. Broker clients ask Octet to call it. See [Device lifecycle](/docs/lfa/concepts/device-lifecycle/).

**Can I get the user's coordinates?**
No. They never leave the phone. LFA can only tell you the region verdict. See
[Trust & Privacy](/docs/lfa/concepts/trust-and-privacy/).

**Why are there no refresh tokens?**
A location proof is a statement about the present moment. If you need fresh
presence, run a fresh login. The location assertion is valid for 120 seconds.

**Why did my OIDC library's `profile email` scopes return nothing?**
LFA advertises `openid octet.location` only and asserts no identity claims for
direct clients. The extras are ignored rather than rejected.

**Is Android supported?**
Yes. The Octet Location app runs on iOS and Android, and both reach the same
`urn:octet:loc:strong` assurance: iOS via Apple App Attest, Android via hardware
key attestation chained to a Google root. Your integration is the same for both
platforms, and the `acr` you receive is identical. The app is on the App Store and
Google Play.

**We use an identity broker such as Okta. Can we broker LFA?**
Okta is supported today through signed authorization requests. Other brokers
that sign requests (RFC 9101) should work. Contact us first. A broker cannot add
`octet_user_id` on the fixed token exchange, so LFA authenticates the front
channel instead: the broker signs its `/authorize` request as a signed request
object (RFC 9101, "JAR"), and LFA binds the device on the `login_hint` inside
that verified object. A client is a broker client when Octet has registered your
broker's issuer and request-signing key for it. You send that public key along
with your issuer URL. For Okta it is the key on the IdP page, not the org key. Device enrollment is forced off for brokers, so a broker user's
new phone is recovered when Octet revokes the old binding with
[`/v1/device/revoke`](/docs/lfa/reference/oidc/#post-v1devicerevoke), rather than with
`octet_device_enroll`. See the [Okta guide](/docs/lfa/integration/okta/).

:::note
Broker setups are configured with us rather than self-served. A signing key is
exchanged and your issuer and request-signing key are pinned on the LFA side. Contact
[hello@octetproof.com](mailto:hello@octetproof.com) and we will validate the flow
against your org before you enable it for users.
:::

## Brokered clients

Signed request object errors appear on LFA's own error page (HTTP 400), with no
redirect, because LFA cannot trust the redirect target until the signed request
is verified. Your app never receives a callback.

| Symptom | Cause and fix |
|---|---|
| LFA shows `this client must send a signed request object` | The client is a broker client, so a plain `/authorize` is rejected. Send the request as a signed object via `request=<JWS>`. On Okta, this means **Enable signed requests** is off, so Okta sends no request object. Turn it on |
| LFA shows `request object must carry login_hint` | The signed object is the only source of the binding identifier. Include `login_hint` inside the signed request object, not just the query string |
| LFA shows `signed request object failed verification` | The key Octet has on file does not match the one your broker signs with, or the object failed an `iss`, `aud`, `exp`, or `nbf` check. See the [OIDC reference](/docs/lfa/reference/oidc/#signed-request-objects-brokers) |
| Login fails at username matching | Leave *IdP Username* at its default. A Factor-only IdP always matches on `preferred_username`, which LFA emits for broker clients, and it ignores any custom expression you set. If it still fails, read `debugContext.debugData.errorMessage` in the System Log. The visible error text names something other than the actual cause |
| `invalid_grant` with a new device rejected for a broker user | Brokers cannot enroll a replacement device. Ask Octet to revoke the old binding with [`/v1/device/revoke`](/docs/lfa/reference/oidc/#post-v1devicerevoke). The next login binds the new phone |

:::note
Signature verification is against **pinned** keys only. LFA does not fetch your
JWKS over the network. Octet pins the key you send. Send Octet the new public
key before you rotate, or valid logins will start failing. Only `RS256` and `ES256` are
accepted.
:::
