Firebase Security

Google Play Integrity API:
Securing Mobile Backends

By Ankit Kumar, Founder of Kyvronix Technologies · 12 min read

Most developers believe that if their API requires a JWT token, it is secure. This is a dangerous misconception. A malicious user can simply use a Man-in-the-Middle (MITM) proxy to intercept the API requests, steal their own JWT token, and then write a Python script to spam your backend endpoints.

To truly secure a mobile API, you must prove not only who is making the request, but what is making the request. Is it your genuine app downloaded from the Play Store running on a real Android device? Or is it a modified APK running on an emulator? Enter the Google Play Integrity API.

1. How Play Integrity Works

The Play Integrity API replaces the deprecated SafetyNet API. The workflow involves three parties: Your App, Google's Servers, and Your Backend.

  1. Your App asks Your Backend for a random string (a "nonce").
  2. Your App sends the nonce to the Google Play Integrity API on the device.
  3. Google verifies the device hardware, OS integrity, and app signature, and returns an encrypted Token.
  4. Your App sends this Token to Your Backend.
  5. Your Backend decrypts the Token (either locally or via Google's REST API) and verifies the nonce matches.

2. Requesting the Integrity Token (Flutter / Kotlin)

In Flutter, you can use the official google_play_integrity package, or write a MethodChannel to the Kotlin implementation.

import com.google.android.play.core.integrity.IntegrityManagerFactory
import com.google.android.play.core.integrity.IntegrityTokenRequest

// Kotlin implementation
fun requestIntegrityToken(nonce: String, callback: (String) -> Unit) {
    val integrityManager = IntegrityManagerFactory.create(context)

    val request = IntegrityTokenRequest.builder()
        .setNonce(nonce) // Base64 url-safe string from your backend
        .build()

    integrityManager.requestIntegrityToken(request)
        .addOnSuccessListener { response ->
            callback(response.token())
        }
        .addOnFailureListener { e ->
            Log.e("Integrity", "Failed to get token", e)
        }
}
  

3. Verifying the Token (NestJS Backend)

When the backend receives the token, it must decrypt it. The easiest way is to use the Google API Client Library and pass the token to Google's servers for decryption.

// NestJS Service
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { google } from 'googleapis';

@Injectable()
export class IntegrityService {
  async verifyToken(integrityToken: string, expectedNonce: string) {
    const auth = new google.auth.GoogleAuth({
      keyFile: 'path/to/your/service-account.json',
      scopes: ['https://www.googleapis.com/auth/playintegrity'],
    });

    const playintegrity = google.playintegrity('v1');

    try {
      const response = await playintegrity.v1.decodeIntegrityToken({
        packageName: 'com.kyvronix.bhumitra',
        auth: auth,
        requestBody: {
          integrityToken: integrityToken,
        },
      });

      const result = response.data.tokenPayloadExternal;

      // 1. Verify Nonce
      if (result.requestDetails.nonce !== expectedNonce) {
          throw new UnauthorizedException('Nonce mismatch. Replay attack detected.');
      }

      // 2. Verify App Recognition (Is it our genuine app?)
      if (result.appIntegrity.appRecognitionVerdict !== 'PLAY_RECOGNIZED') {
          throw new UnauthorizedException('App is not recognized (Sideloaded/Modified).');
      }

      // 3. Verify Device Integrity (Is it a real Android device?)
      if (!result.deviceIntegrity.deviceRecognitionVerdict.includes('MEETS_DEVICE_INTEGRITY')) {
          throw new UnauthorizedException('Device is compromised (Rooted/Emulator).');
      }

      return true;
    } catch (e) {
      throw new UnauthorizedException('Integrity verification failed');
    }
  }
}
  

4. The Cost of Security

Play Integrity calls are slightly slow and require network requests to Google. You should not require an Integrity token on every single API request. Instead, require it on critical endpoints (like Login, Registration, or Payment) or generate a short-lived session token (valid for 1 hour) upon successful Integrity verification.

Conclusion

If you are building an app where data scraping, fake accounts, or automation bots are a concern, JWTs are not enough. The Google Play Integrity API is the definitive way to ensure your API is only communicating with genuine instances of your application.

— Ankit Kumar