# Device Fingerprint

This page explains how the device fingerprint that tokens are bound to is generated, where it is checked, and how detection actually behaves.

## Generation

`CreateFingerprint()` (`src/CreateFingerprint.ts`) builds a JSON object from these four values and takes its SHA-256 hex digest:

| Field | Source |
|---|---|
| `os` | User-Agent match |
| `browser` | User-Agent match |
| `device` | User-Agent match |
| `deviceId` | `X-Device-ID` header → `req.body.deviceId` → `"Unknown"` |

When `req.session.fp` exists (for example, stored yourself with `express-session`), every API uses it instead of recomputing.

## Detection Rules

Matching runs top to bottom and **the first hit wins**:

| Field | Order |
|---|---|
| `os` | `Windows` → `Macintosh\|Mac OS X` → `Linux` → `Android` → `iPhone\|iPad\|iPod` → `Unknown_OS` |
| `browser` | `Edge\|Edg` → `Firefox` → `Chrome` → `Safari` → `Opera\|OPR` → `Unknown_Browser` |
| `device` | `iPad` → `iPhone\|iPod\|Android.*Mobile\|BlackBerry\|IEMobile\|Opera Mini` → `Desktop` |

Because matching stops at the first hit, some results are counterintuitive:

| User-Agent | Actual result | Why |
|---|---|---|
| Android | `os: Linux` | Android UAs contain `Linux`, which is checked before `Android` |
| iPhone / iPad | `os: MacOS` | iOS UAs contain `like Mac OS X` |
| Opera | `browser: Chrome` | Opera UAs contain `Chrome` |

These values only feed the hash; binding works as long as the same device produces the same result each time.

## Where It Is Checked

| When | Result on mismatch |
|---|---|
| `fp` in a valid Access Token | `isError: true` |
| `fp` decoded from an expired Access Token | `isError: true` |
| `fp` in Redis refresh data | `isError: false` (guest) |

Related: [Token Transport](/token-transport), [Client Integration](/client-integration)
