# Login with IDfier (/docs/identity/login)

This is how IDfier's own products, IDfier Sign and Rezervoj, log people in.
The [Next.js example](/docs/identity/login/nextjs) has the complete code.

## Flow [#flow]

```
Browser                  Your server                      IDfier API
   │  "Log in"                │                               │
   │ ────────────────────────▶│ POST /business/auth/request   │
   │                          │ ─────────────────────────────▶│
   │                          │ ◀── referenceId, qrReferenceSecret, createdAt
   │ ◀── the three values ────│ (and a cookie with referenceId)
   │                          │                               │
   │ shows the QR code, refreshed every 2 s                   │
   │ (on a phone: a button that opens the IDfier app)         │
   │                          │                               │
   │ polls your server ──────▶│ POST /business/auth/result    │
   │                          │ ─────────────────────────────▶│
   │                          │ ◀── session.status            │
   │        ... customer approves in the IDfier app ...       │
   │ "approved" ─────────────▶│ reads the result once more,   │
   │                          │ checks the cookie, logs in    │
   │ ◀── your session ────────│                               │
```

## Steps [#steps]

<Steps>
<Step>

### Start a session (server) [#start-a-session-server]

Your server calls [`POST /business/auth/request`](/docs/identity/reference/login/createLoginSession)
and returns `referenceId`, `qrReferenceSecret` and `createdAt` to the browser.

Also set an **httpOnly cookie with the `referenceId`**. Only the browser that
started the login may finish it (step 4). Without this, anyone who learns a
`referenceId`, for example by seeing the QR code over someone's shoulder,
could finish that customer's login in their own browser.

</Step>
<Step>

### Show the QR code (browser) [#show-the-qr-code-browser]

Build the QR payload from the three values and refresh it every ~2 seconds:
the payload contains the seconds since `createdAt`, and the app accepts only a
small time window. The format is in [QR code payload](/docs/identity/login/qr-code).

On a phone, show a button instead that opens the app with the same payload:
`https://www.idfier.com/auth?authData=<payload>`.

</Step>
<Step>

### Wait for approval (browser → your server → IDfier) [#wait-for-approval-browser--your-server--idfier]

Poll your own server, which calls [`POST /business/auth/result`](/docs/identity/reference/login/getLoginResult).
Start at about every 2 seconds and back off to about 10:

| `session.status` | Do |
| --- | --- |
| `pending` | Not scanned yet; keep polling |
| `started` | Scanned; stop refreshing the QR code and ask the customer to approve |
| `approved` | Go to step 4 |
| `denied`, `error` | Stop and tell the customer |
| `expiresAt` passed | The session expired (after 5 minutes); offer a new one |

Or set a [webhook](/docs/identity/webhooks) when you create the session.

</Step>
<Step>

### Log the customer in (server) [#log-the-customer-in-server]

When the browser reports `approved`, don't take the customer's data from the
browser. On your server:

1. Check that the `referenceId` matches the cookie from step 1.
2. Call `POST /business/auth/result` and check that `data.session.status` is
   `approved`.
3. Use `data.user` from that response: find or create the customer by
   `user.id`, and issue your own session.
4. Delete the cookie, so the login can't be reused.

</Step>
</Steps>

## The result [#the-result]

```json
{
  "status": 100,
  "data": {
    "user": {
      "id": "u_3f9a2c…",
      "fullname": "Anna Lindqvist",
      "email": "anna@example.com"
    },
    "session": {
      "referenceId": "550e8400-e29b-41d4-a716-446655440000",
      "status": "approved",
      "method": "idfier",
      "expiresAt": "2026-10-31T10:05:00.000Z",
      "createdAt": "2026-10-31T10:00:00.000Z"
    }
  }
}
```

- `user` is `null` until the session is `approved`. It always has `id`, plus
  the fields in your [scopes](/docs/identity/scopes) that IDfier has for the
  customer.
- `session.method` is `idfier` (the IDfier app) or `eudi_wallet` (an [EU
  Digital Identity Wallet](/docs/identity/eu-wallet)), once approved.
- A session belongs to the first person who scans it: only they can approve or
  deny it, and only before it expires. Its scopes are fixed when it is created,
  so the result has exactly what the customer was shown and approved.

## Recognise returning customers [#recognise-returning-customers]

`user.id` is stable for a customer within your business, and different for
every other business. Key the customer's account on it, not on email or name:
a customer may not share them, and they can change.

## Checklist [#checklist]

- [ ] `clientSecret` only on the server
- [ ] The QR code refreshes every 2 seconds; a phone gets a button instead
- [ ] The result is read on the server, never taken from the browser
- [ ] The `referenceId` is tied to the browser with an httpOnly cookie
- [ ] Accounts are keyed on `user.id`
- [ ] Expired, denied and failed sessions offer a way to start again
