Attest (device attestation)
The runtime SDK runs on the device, so a fully compromised device can lie about what it reports. Attest closes that gap: the app exchanges the SDK's runtime report — plus a platform attestation (Play Integrity on Android, App Attest on iOS) — for a short signed token. Your backend verifies that token before a sensitive action (a Pix transfer, a login, a password change).
Attest is available from the Startup plan.
How it works
- Your backend generates a
noncefor the operation and sends it to the app. - The app calls
Moatgator.attest(nonce). The SDK collects its findings, asks the platform for an attestation bound to that nonce, and exchanges both atPOST /v1/attest/token. - Moatgator verifies the platform attestation server-side and returns a token (an EdDSA JWT, valid for 5 minutes, bound to your app, the platform and the nonce).
- The app sends the token to your backend. Your backend verifies it — offline with the public JWKS, or with
POST /v1/attest/verify.
The verdict is trusted, suspicious or untrusted, with the reasons and the evidence that backed it (rasp alone means no platform attestation was available).
Configure (console)
In Console → your app → Attest:
- Play Integrity (Android): paste the Google Cloud service account JSON for your project and the package name. It is stored encrypted and never shown again.
- App Attest (iOS): set your Apple Team ID and bundle id.
- Require platform attestation: when on, a token with only the runtime report (no platform attestation) is
untrusted.
App side
val r = Moatgator.attest(nonce = order.nonce, integrity = { challenge -> playIntegrityToken(challenge) })
if (!r.trusted) blockPayment()
The integrity provider is where you call the Play Integrity API; the SDK does not depend on Google's library. Without a provider, the token carries only the runtime report.
Backend verification
import { createVerifier } from "@moatgator/attest";
const attest = createVerifier({ appId: process.env.MOATGATOR_APP_ID, env: "live" });
const r = await attest.verify(req.header("x-moatgator-attest"), { nonce: order.nonce, platform: "android" });
if (!r.trusted) return res.status(403).json({ error: "device_not_trusted", reasons: r.reasons });
Verifiers also exist for Python and Go. With any JOSE library: fetch /.well-known/jwks.json, accept only alg: EdDSA, and check typ == "attest", iss, aud (your app id), exp and nonce.
Limits
- The nonce is yours: generate a fresh, unpredictable value per operation on the backend and check it on the way back.
- App Attest requires building on macOS with Xcode; the
Moatgator.attest()helper is on Android today, with iOS to follow. - Platform attestation is only as strong as the platform provides; the runtime report adds device-side signals but no single signal is proof on its own.