Skip to main content

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​

  1. Your backend generates a nonce for the operation and sends it to the app.
  2. The app calls Moatgator.attest(nonce). The SDK collects its findings, asks the platform for an attestation bound to that nonce, and exchanges both at POST /v1/attest/token.
  3. 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).
  4. 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.