Cross-platform device attestation for Capacitor:
- iOS: Apple App Attest (
DeviceCheck) - Android: Google Play Integrity Standard API token attestation
This plugin gives you one JavaScript API for both platforms while using only the native attestation systems:
- iOS uses Apple App Attest.
- Android uses Google Play Integrity Standard API.
- No custom cryptography, no random app-side security scheme.
- Same JS methods and same output shape on both platforms (
platform,format,token,keyId).
Use it to harden login, account recovery, payments, promo abuse checks, and other high-risk endpoints.
Attestation only adds value when validated on your backend.
- Never trust client-side success alone.
- Always verify App Attest / Play Integrity payloads server-side.
- Reject tokens/assertions that fail signature, nonce, app identity, or environment checks.
Recommended JS API:
prepare()createAttestation()createAssertion()
Legacy aliases are still available for compatibility:
generateKey()=>prepare()attestKey()=>createAttestation()generateAssertion()=>createAssertion()
On both iOS and Android, results include:
platform:iosorandroidformat:apple-app-attestorgoogle-play-integrity-standardtoken: normalized token field for backend verification
The most complete doc is available here: https://capgo.app/docs/plugins/app-attest/
| Plugin version | Capacitor compatibility | Maintained |
|---|---|---|
| v8.. | v8.. | ✅ |
| v7.. | v7.. | On demand |
| v6.. | v6.. | ❌ |
| v5.. | v5.. | ❌ |
Note: The major version of this plugin follows the major version of Capacitor. Use the version that matches your Capacitor installation (for example, plugin v8 for Capacitor 8).
bun add @capgo/capacitor-app-attest
bunx cap sync- Open your app target in Xcode.
- Enable the
App Attestcapability (underSigning & Capabilities). - Run on a physical device (App Attest is not available in all simulator contexts).
- Enable Play Integrity API in your Google Cloud project.
- In Play Console, configure integrity access for your app.
- Set
cloudProjectNumberin Capacitor config:
// capacitor.config.ts
plugins: {
AppAttest: {
cloudProjectNumber: '123456789012'
}
}You can also pass cloudProjectNumber directly in method options.
This plugin uses the Standard Integrity API flow on Android (prepareIntegrityToken + request).
On Android, prepare() prepares the native Standard Integrity provider and returns a handle (keyId) for subsequent calls.
import { AppAttest } from '@capgo/capacitor-app-attest';
const support = await AppAttest.isSupported();
if (!support.isSupported) {
return;
}
const prepared = await AppAttest.prepare();
const keyId = prepared.keyId;
const registration = await AppAttest.createAttestation({
keyId,
challenge: 'server-registration-challenge',
});
const assertion = await AppAttest.createAssertion({
keyId,
payload: 'request-payload-or-nonce-from-backend',
});
// registration.token and assertion.token are verified server-side.
console.log(registration, assertion);Your backend must branch by platform/format and run the native verification flow for that platform.
Registration (createAttestation):
- Generate a one-time random challenge on your backend.
- Send that challenge to the app.
- App calls
prepare()once, thencreateAttestation({ keyId, challenge }). - Backend verifies attestation with Apple App Attest rules:
- Certificate chain is valid and anchored to Apple App Attest.
- App identity matches your bundle/team.
clientDataHashcorresponds toSHA256(challenge).
- Store key material state for this device/user (
keyId, public key, counter metadata from your verifier).
Request protection (createAssertion):
- Backend builds a per-request one-time payload/nonce (or canonical request hash input).
- App calls
createAssertion({ keyId, payload }). - Backend verifies assertion signature using the stored iOS App Attest key context.
- Enforce replay protection (single-use nonce + expiration + monotonic counter checks from verifier output).
Registration (createAttestation):
- Generate a one-time random challenge on your backend.
- App calls
createAttestation({ keyId, challenge }). - Backend decodes the returned Play Integrity token with Google Play Integrity server API (
decodeIntegrityToken). - Validate at minimum:
requestDetails.requestHashequalsbase64url(SHA256(challenge)).appIntegrity.packageNamematches your app id.appIntegrity.certificateSha256Digestcontains your release signing cert digest.appIntegrity.appRecognitionVerdictmeets your policy (commonlyPLAY_RECOGNIZED).deviceIntegrity.deviceRecognitionVerdictmeets your policy (for example includesMEETS_DEVICE_INTEGRITY).
- Persist attestation decision/state tied to user/device session.
Request protection (createAssertion):
- Backend issues a one-time request payload/nonce.
- App calls
createAssertion({ keyId, payload }). - Backend decodes token and checks
requestHash === base64url(SHA256(payload)). - Reject reused/expired payloads and enforce your integrity verdict policy.
flowchart TD
A[Backend creates one-time challenge/payload] --> B[App calls AppAttest plugin]
B --> C{platform}
C -->|iOS| D[Apple App Attest]
C -->|Android| E[Play Integrity Standard]
D --> F[token + format + platform + keyId]
E --> F
F --> G[App sends token + context to backend]
G --> H{backend verification by format}
H -->|apple-app-attest| I[App Attest verification]
H -->|google-play-integrity-standard| J[Play Integrity decode + policy checks]
I --> K[allow or deny]
J --> K
sequenceDiagram
participant App as Mobile App (iOS)
participant Plugin as @capgo/capacitor-app-attest
participant Apple as Apple App Attest
participant BE as Backend
BE->>App: registrationChallenge
App->>Plugin: prepare()
Plugin->>Apple: generateKey()
Apple-->>Plugin: keyId
Plugin-->>App: keyId
App->>Plugin: createAttestation(keyId, challenge)
Plugin->>Apple: attestKey(keyId, SHA256(challenge))
Apple-->>Plugin: attestationObject
Plugin-->>App: token + platform + format + keyId + challenge
App->>BE: token + keyId + challenge
BE->>BE: verify cert chain + app identity + clientDataHash
BE-->>App: registration accepted/rejected
BE->>App: requestPayload/nonce
App->>Plugin: createAssertion(keyId, payload)
Plugin->>Apple: generateAssertion(keyId, SHA256(payload))
Apple-->>Plugin: assertion
Plugin-->>App: token + platform + format + keyId + payload
App->>BE: token + keyId + payload
BE->>BE: verify signature + counter + replay policy
BE-->>App: request allowed/denied
sequenceDiagram
participant App as Mobile App (Android)
participant Plugin as @capgo/capacitor-app-attest
participant PlaySDK as Play Integrity SDK
participant BE as Backend
participant Google as Google decodeIntegrityToken API
Note over App,BE: One-time provider preparation
App->>Plugin: prepare({ cloudProjectNumber })
Plugin->>PlaySDK: prepareIntegrityToken(...)
PlaySDK-->>Plugin: tokenProvider handle (keyId)
Plugin-->>App: keyId
BE->>App: registrationChallenge
App->>Plugin: createAttestation(keyId, challenge)
Plugin->>PlaySDK: request(requestHash=base64url(SHA256(challenge)))
PlaySDK-->>Plugin: integrityToken
Plugin-->>App: token + platform + format + keyId + challenge
App->>BE: token + keyId + challenge
BE->>Google: decodeIntegrityToken(token)
Google-->>BE: decoded integrity payload
BE->>BE: verify requestHash + packageName + cert digest + verdict policy
BE-->>App: registration accepted/rejected
BE->>App: requestPayload/nonce
App->>Plugin: createAssertion(keyId, payload)
Plugin->>PlaySDK: request(requestHash=base64url(SHA256(payload)))
PlaySDK-->>Plugin: integrityToken
Plugin-->>App: token + platform + format + keyId + payload
App->>BE: token + keyId + payload
BE->>Google: decodeIntegrityToken(token)
Google-->>BE: decoded integrity payload
BE->>BE: verify requestHash + replay/ttl + verdict policy
BE-->>App: request allowed/denied
Registration payload from app to backend:
{
"platform": "ios | android",
"format": "apple-app-attest | google-play-integrity-standard",
"keyId": "string",
"challenge": "string",
"token": "string"
}Assertion payload from app to backend:
{
"platform": "ios | android",
"format": "apple-app-attest | google-play-integrity-standard",
"keyId": "string",
"payload": "string",
"token": "string"
}- Attestation challenges/payloads must be generated server-side.
- Treat every challenge/payload as single-use with short TTL.
- Keep allowlists for package id and cert digest by environment (dev/staging/prod).
- Log verification failures with reason codes; never silently accept failures.
- Do not use this plugin as a replacement for auth/session controls, use it as an additional trust signal.
isSupported()prepare(...)createAttestation(...)createAssertion(...)storeKeyId(...)getStoredKeyId()clearStoredKeyId()generateKey(...)attestKey(...)generateAssertion(...)- Interfaces
- Type Aliases
Unified cross-platform attestation plugin for Capacitor.
Recommended methods:
prepare()createAttestation()createAssertion()
Legacy aliases are still available for compatibility:
generateKey()attestKey()generateAssertion()
isSupported() => Promise<IsSupportedResult>Checks whether native attestation is available on this device.
Returns: Promise<IsSupportedResult>
prepare(options?: PrepareOptions | undefined) => Promise<PrepareResult>Prepares attestation state and returns the key handle used for later calls.
iOS: generates a real App Attest key identifier. Android: prepares a Play Integrity Standard token provider handle.
| Param | Type |
|---|---|
options |
PrepareOptions |
Returns: Promise<PrepareResult>
createAttestation(options: CreateAttestationOptions) => Promise<CreateAttestationResult>Creates a registration attestation token bound to a backend-issued challenge.
iOS: returns App Attest attestationObject.
Android: returns Play Integrity Standard token.
| Param | Type |
|---|---|
options |
CreateAttestationOptions |
Returns: Promise<CreateAttestationResult>
createAssertion(options: CreateAssertionOptions) => Promise<CreateAssertionResult>Creates a request assertion token bound to a request payload.
iOS: returns App Attest assertion. Android: returns Play Integrity Standard token.
| Param | Type |
|---|---|
options |
CreateAssertionOptions |
Returns: Promise<CreateAssertionResult>
storeKeyId(options: StoreKeyIdOptions) => Promise<OperationResult>Stores/prepares a key identifier for reuse.
iOS: persists in UserDefaults. Android: prepares a native Play Integrity provider for this key id in memory (process lifetime).
| Param | Type |
|---|---|
options |
StoreKeyIdOptions |
Returns: Promise<OperationResult>
getStoredKeyId() => Promise<GetStoredKeyIdResult>Returns the currently stored/prepared key identifier.
Android value is only available while the process is alive.
Returns: Promise<GetStoredKeyIdResult>
clearStoredKeyId() => Promise<OperationResult>Clears stored/prepared key identifiers.
Returns: Promise<OperationResult>
generateKey(options?: PrepareOptions | undefined) => Promise<GenerateKeyResult>Legacy alias for prepare().
| Param | Type |
|---|---|
options |
PrepareOptions |
Returns: Promise<PrepareResult>
attestKey(options: AttestKeyOptions) => Promise<AttestKeyResult>Legacy alias for createAttestation().
| Param | Type |
|---|---|
options |
CreateAttestationOptions |
Returns: Promise<AttestKeyResult>
generateAssertion(options: GenerateAssertionOptions) => Promise<GenerateAssertionResult>Legacy alias for createAssertion().
| Param | Type |
|---|---|
options |
CreateAssertionOptions |
Returns: Promise<GenerateAssertionResult>
| Prop | Type |
|---|---|
isSupported |
boolean |
platform |
AttestationPlatform |
format |
AttestationFormat |
| Prop | Type |
|---|---|
keyId |
string |
platform |
AttestationPlatform |
format |
AttestationFormat |
| Prop | Type | Description |
|---|---|---|
cloudProjectNumber |
string |
Android only. Google Cloud project number for Play Integrity. Can be set globally in Capacitor config via plugins.AppAttest.cloudProjectNumber. |
| Prop | Type | Description |
|---|---|---|
token |
string |
Unified attestation token value. iOS: base64 App Attest attestation. Android: Play Integrity token. |
keyId |
string |
|
challenge |
string |
|
platform |
AttestationPlatform |
|
format |
AttestationFormat |
| Prop | Type |
|---|---|
keyId |
string |
challenge |
string |
cloudProjectNumber |
string |
| Prop | Type | Description |
|---|---|---|
token |
string |
Unified assertion token value. iOS: base64 App Attest assertion. Android: Play Integrity token. |
keyId |
string |
|
payload |
string |
|
platform |
AttestationPlatform |
|
format |
AttestationFormat |
| Prop | Type |
|---|---|
keyId |
string |
payload |
string |
cloudProjectNumber |
string |
| Prop | Type |
|---|---|
success |
boolean |
| Prop | Type |
|---|---|
keyId |
string |
cloudProjectNumber |
string |
| Prop | Type |
|---|---|
keyId |
string | null |
hasStoredKey |
boolean |
| Prop | Type | Description |
|---|---|---|
attestation |
string |
Legacy field equal to token. |
| Prop | Type | Description |
|---|---|---|
assertion |
string |
Legacy field equal to token. |
'ios' | 'android' | 'web'
'apple-app-attest' | 'google-play-integrity-standard' | 'web-fallback'
iOS App Attest flow is inspired by the original plugin from ludufre/capacitor-app-attest.
Android support in this plugin is implemented with Google Play Integrity to provide equivalent attestation coverage.
