Docs/Getting Started/Quick Start

Quick Start

A complete LFA login, end to end: redirect the browser out, let the phone prove presence, exchange the code, check one claim. Nothing here requires a specific framework. It is the plain authorization-code flow plus one extra token field.

Have the prerequisites ready: a client_id, a client_secret, a registered redirect URI, and a phone with the Octet Location app inside your policy's region.

Note

This is the direct integration walkthrough: your backend does the code exchange itself and sends octet_user_id on /token. Identity brokers (Okta, Microsoft Entra ID, Auth0) do not follow these steps. They cannot add octet_user_id to their fixed token exchange, so they authenticate the /authorize request with a signed request object instead. If you are wiring up a broker, follow Okta integration, not this page.


1. Redirect to /authorize

Send the browser to LFA with standard authorization-code and PKCE parameters. state and nonce are both required (stricter than the OIDC spec):

https://factor.octetproof.com/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.example/callback
  &scope=openid%20octet.location
  &state=RANDOM_STATE
  &nonce=RANDOM_NONCE
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256

The browser lands on the LFA login page: a QR code, plus an app link if the user is already on their phone. The login session lives about 90 seconds.

2. The user proves presence

The user scans the QR code with the Octet Location app. The app runs the location check, produces a hardware-attested proof, and submits it directly to LFA while the browser waits. On success the browser returns to your redirect_uri with ?code=…&state=…. On failure (wrong region, spoofing detected, attestation failure) it returns error=access_denied.

3. Exchange the code with octet_user_id

Verify state, then exchange the code within 60 seconds. The exchange is standard except for one field: octet_user_id, your identifier for the user who is signing in.

curl -s https://factor.octetproof.com/token \
  -d grant_type=authorization_code \
  -d code=THE_CODE \
  -d redirect_uri=https://yourapp.example/callback \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d code_verifier=THE_PKCE_VERIFIER \
  -d octet_user_id=usr_1234

On the user's first login this binds their phone to usr_1234. On every later login it checks that the phone is still that user's bound device. Omitting the field fails with invalid_request. A mismatched device fails with invalid_grant. See Device lifecycle.

The response carries the tokens and a convenience mirror of the location assertion:

{
  "token_type": "Bearer",
  "access_token": "…",
  "id_token": "…",
  "location_assertion": "…"
}

4. Validate and gate

Validate the id_token as you would from any OIDC IdP: check the ES256 signature against the JWKS, and check iss, aud, exp, and your nonce echo. Then the gate is a single check:

"acr": "urn:octet:loc:strong"

acr: urn:octet:loc:strong means the proof was hardware-verified end to end and the policy passed. The sub claim is the octet_user_id you sent. The signed region statement itself travels as the octet_location_assertion claim (and the top-level location_assertion mirror). Most integrations never open it. It is there for audit trails and downstream verification.

Errors

Every error in this flow is indexed in Troubleshooting: expired QR, expired code, missing octet_user_id, a device bound to a different user.

Where to go next