Skip to main content
Registration is a one-time-per-(app_id, device) handshake that establishes a hardware-backed device identity with the auth service. After it completes, the SDK can sign every outgoing request without further server round-trips.

Steps (normative)

When used through Synheart Core, steps 1, 6, and 8 are performed by the Synheart runtime, which calls the SDK’s crypto callbacks for steps 3, 4, 5.

1. Request a challenge

Response:
Challenge TTL is 90 seconds (RFC §7.3). Single-use, server-enforced. If the SDK fails to complete steps 2–7 within the TTL, it must request a new challenge.

2. Generate the keypair

The key MUST be non-exportable. The Dart SDK never sees the private key; only signRequest results.

3. Compute binding nonce

This binds the attestation proof to the specific public key the SDK is registering. The server recomputes the nonce in step 7 and rejects mismatches — preventing attestation reuse with a different key.

4. Attest with platform provider

Returns the attestation proof (Apple) or integrity token (Google). The SDK does not validate it — that’s the server’s job in step 7. The Dart facade returns AttestationUnavailable if the platform doesn’t support attestation.

5. Register with auth service

Server verifies (RFC §7.2.6):
  1. GETDEL the challenge from Redis (single-use, atomic).
  2. Recompute SHA256(challenge + base64(public_key)) and compare with the attestation proof’s nonce field.
  3. Verify proof against Apple/Google attestation APIs.
  4. Persist (app_id, device_id, public_key, platform, status, registered_at).
  5. Return { device_id, status }.
status is one of "registered" | "pending" | "rejected".

6. Persist locally

The SDK stores per app_id: Never persisted: private key bytes (live only in hardware), raw attestation tokens.

Idempotency

registerDevice(appId) is idempotent: subsequent calls return { status: alreadyRegistered, deviceId: <existing> } without touching the network. Call it at app start regardless — the SDK fast-paths when isRegistered(appId) is true.

Failure paths

Rotation flow (RFC §8.3)

rotateKey(appId) replaces the signing key while preserving the device_id:
Rotation policy (RFC §8.3): SDK SHOULD rotate every 90 days; MUST rotate immediately on platform key-compromise signals; server MAY reject keys older than its configured maximum age.

Dev / QA bypass (RFC §14)

Attestation bypass exists for emulator/CI and is strictly controlled:
  • Server-side allowlist on app_id + build channel (dev/staging only).
  • "development_integrity_allowed": true on the platform-side app record.
  • Mobile SDK MUST set X-Synheart-Dev-Mode: true header so the server tracks dev vs. prod traffic.
  • Bypass MUST be compile-time gated (#if DEBUG / BuildConfig.DEBUG) — never runtime-toggleable.
There are no hidden bypasses. A production build that emits X-Synheart-Dev-Mode: true is treated as a security incident.