Security  /  App identity & attestation

App identity & attestation

For native and desktop apps, the publishable key is bound to your app two ways: a Bundle ID ties the key to your app's identity, and attestation cryptographically proves each request came from a genuine, unmodified build of it. The SDK attaches the proof automatically — you write none of it — but there are a couple of invisible requirements that trip everyone up. This page is the model, the gotchas, and how to register.

The model

The SDK sends an X-Bundle-Id header on every request, plus a platform-specific proof token. Onelo verifies both against what you've registered.

Enforcement activates once you register at least one bundle. Before that — a brand-new app with nothing registered — the check is fail-open (so you can build without locking yourself out). Registering your first bundle turns enforcement on, and going live requires one.

Web apps aren't attested — they're gated by Allowed domains instead. A server (Python/Node/PHP) does no client attestation at all — its trust is the secret key, kept server-side.

Availability by platform

Attestation is live and verified end-to-end on Apple platforms today. The other native platforms are on the way — until then, don't rely on client attestation for them.

PlatformMechanismStatus
iOSApple App Attest (X-Attest-Token)Available
macOSApple App Attest (with the entitlement), else a codesign fingerprint (X-Codesign-Fingerprint)Available
AndroidGoogle Play Integrity (X-Integrity-Token) — full server-side verdict + signing-cert checks, enforcedAvailable
React Native · FlutterApp Attest (iOS side) + Play Integrity (Android side) — in progress, shipping soonComing soon
Electron (Windows / macOS)A codesign fingerprint (X-Codesign-Fingerprint) per signed OS. Linux is unsigned → falls back to the Bundle ID + secret key.Available
Web— (use Allowed domains)N/A
Server (Python/Node/PHP)— (trust is the secret key)N/A
Don't rely on it before it ships. Attestation is live and enforced for iOS, macOS, Android and Electron. On React Native and Flutter it is still being finished, so treat those apps as protected by their Bundle ID and keys only, and keep any security decision that matters on your server. This page is updated the moment each one is live.

The gotchas that waste the most hours

iOS — a real device, not the Simulator

App Attest needs the Secure Enclave, which the Simulator doesn’t have: DCAppAttestService.isSupported == false there, so the SDK silently skips it and nothing registers. Run on a physical iPhone/iPad. You also need the App Attest capability on the target (Xcode → Signing & Capabilities).

macOS — a signed build

Most macOS apps (SwiftPM / Developer ID / adhoc) don’t carry the App Attest entitlement, so they fall back to codesign — which needs a properly signed build (Developer ID or Apple Development). An unsigned / adhoc build produces no fingerprint → no attestation, silently.

Everything — an open discovery window

Registration only happens while a discovery window is open (see below) and the app runs on a real device. No window → nothing registers, even though the app runs fine.

It’s automatic, so failure is quiet

The SDK never throws for a missing/unsupported attestation — it just sends what it can. That’s why “it doesn’t work” is usually one of the three above, not a code bug.

Registering your app — the discovery window

Registration is trust-on-first-use. You open a short window, run the app once, and the bundle registers itself — no hand-typing (a typed Bundle ID doesn't count for going live).

StepWhat you do
1In the dashboard, open your app → Security → App Identity and open a discovery window.
2Run your app on a real device (iOS) or a signed build (macOS) with the SDK initialised.
3The SDK submits its attestation; Onelo verifies it and auto-registers the bundle / fingerprint. The App Identity panel updates live.
The window requires an authenticated dashboard session to open (that's the anti-squatting guard — nobody can register a bundle on your app but you), and it closes automatically after the first successful registration.

Multi-target apps — register each platform separately

A multi-target app (an Electron app shipping macOS and Windows) registers each platform on its own — from that platform's machine, at its own time. You do not need them all running in one window (you rarely have a Mac and a Windows box open at once).

WhenWhat happens
Today, on your MacOpen a discovery window → run the macOS build → the macOS signing cert auto-registers → the window closes.
Later, from WindowsRe-open the window → run the Windows build → the Windows cert co-registers alongside the macOS one (added to the set — it never replaces it).
Once your first bundle is registered, the dashboard swaps the initial discovery panel for an “Open discovery window for another OS/platform” button — that's the way back in. Re-opening is a first-class path: still dev-initiated, still 10 minutes, still closes after one registration, and co-registration is additive — it never removes or locks out a cert you already have.

Electron across Windows, macOS & Linux

A common question: shipping one Electron app to Windows, macOS and Linux — do you need a separate app or key per OS? No. One app, one key, everywhere. Only one thing is per-OS, and it isn't what most people expect.

AspectHow it works across OS
One key = every moduleAuth, store, customer portal, waitlist, features, feedback, roadmap and monitor are OS-agnostic HTTPS calls. One publishable key, one OneloConfig, the same code — works on Windows, macOS and Linux with nothing per-OS.
Identity = the install, not the OSEach installation gets a per-install UUID (X-Onelo-Instance-Id). A Windows install, a Mac install and a Linux install are three different instances — the backend binds realtime, feature discovery (trust-on-first-use per app + instance) and monitor by instance, not by OS.
Users are cross-instanceSubscription, entitlement and feature targeting follow the account, not the install — a purchase on Windows unlocks on the same account’s Mac.
Codesign is the only per-OS partmacOS (Developer ID) and Windows (Authenticode) sign with different certificates → different fingerprints. Linux is unsigned, so codesign is inert (fail-open) and integrity rests on the Bundle ID + your backend’s secret key.

Your codesign fingerprints are a set — one app, one fingerprint per signed OS, and enforcement is simply “is this cert in the set?”. Your Bundle ID (the Electron appId, e.g. com.company.app) is the same on every OS, so it's registered once.

Set bundleId in your Electron config — this trips people up. The SDK only sends X-Bundle-Id when you pass bundleId. On a signed build under enforcement, if you omit it every bundle-gated request gets bundle_id_mismatch (403) and auth silently fails. Two separate layers: (a) registering the bundle on Onelo's side is automatic (no manual entry in the dashboard), but (b) you still must provide bundleId in the SDK config. (On iOS/macOS/Android the id comes from native attestation automatically; Electron reads it from your config.) SDK ≥ 0.36.5 warns at startup if a signed build has no bundleId.
js
const auth = new OneloElectronAuth({
  publishableKey: 'onelo_pk_live_YOUR_KEY',
  apiUrl: '…',        // your Onelo API URL, from the dashboard SDK page
  protocol: 'myapp',
  bundleId: 'com.company.app', // REQUIRED for X-Bundle-Id — must match your signed appId
})

In the dashboard (App → Security), the Codesign fingerprints section is grouped per OS — a macOS card and a Windows card, each with its certs, status, and the exact command to extract a fingerprint. An Unassigned card holds any cert registered before OS tagging, with a “Set OS” dropdown. The OS is filled automatically when the SDK reports it (via X-Codesign-Platform), or by hand when you add a cert.

OSExtract the signing-cert fingerprint
macOScodesign -d --extract-certificates … then shasum -a 256
WindowsGet-AuthenticodeSignature … .SignerCertificate.GetCertHashString()
Linux— unsigned; no codesign attestation (relies on Bundle ID + secret key).
The “one key = every module” and instance-identity model above isn't only a security detail — it's how the SDK works on desktop generally. See How it works.

How Apple App Attest works

For the curious — this is Apple's three-step model, which Onelo verifies server-side. You don't call any of it; the SDK does.

StepWhenWhat happens
generateKey()Once per installGenerates a key pair; the private key stays in the device’s Secure Enclave (hardware) and never leaves.
attestKey()Once per keySends the attestation; the server validates the certificate chain up to Apple’s App Attestation Root CA, reconstructs the nonce from the challenge, and extracts the public key — then registers the bundle (Onelo: trust-on-first-use, in the discovery window).
generateAssertion()Each later requestSigned on-device (Apple’s servers no longer involved); the server verifies the signature with the stored public key plus a counter, to stop replay.
Where Onelo is today: Apple SDKs already send a rolling generateAssertion proof on every request, and the server verifies it (signature, single-use challenge, replay counter). You don't wire anything — it rides along with the calls you already make.

Next

App identity & attestation — Security — Onelo Docs