Implementing Firebase App Check for Mobile App Security

TL;DR: A step-by-step implementation guide for Firebase App Check on iOS and Android to prevent API abuse and ensure request authenticity.

Mobile APIs are constantly under attack from bots, scrapers, and malicious actors. Firebase App Check provides a way to verify that incoming requests originate from your legitimate app. This guide explains how to implement it effectively.

What is Firebase App Check?

Firebase App Check is an attestation service that helps protect your backend resources from abuse. It works by:

  1. Verifying that requests come from your authentic app
  2. Blocking requests from unauthorized sources
  3. Requiring minimal code changes to implement

How It Works

Token Generation Flow

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  Your App   │────▶│   Firebase  │────▶│  Attestation│
│             │     │  App Check  │     │   Provider  │
└─────────────┘     └─────────────┘     └─────────────┘
       │                   │                    │
       │                   │    Device/App      │
       │                   │◀── Verified ───────┘
       │                   │
       │◀── App Check Token ┘

       │     (Token stored locally for 24 hours)


┌─────────────┐     ┌─────────────┐
│   API Call  │────▶│   Backend   │
│ with Token  │     │  Verifies   │
└─────────────┘     └─────────────┘

Process Overview

  1. Token Generation: A Firebase App Check token is generated once every 24 hours
  2. Token Storage: The token is saved in local storage for the duration of its validity
  3. Token Usage: When API calls are made, the stored token is included in the request header
  4. Backend Verification: The token is verified on the backend to ensure validity

Implementation Steps

1. Firebase Console Setup

First, enable App Check in your Firebase project:

  1. Go to Firebase Console → App Check
  2. Register your apps (iOS and Android)
  3. Choose attestation providers:
    • iOS: DeviceCheck or App Attest
    • Android: Play Integrity or SafetyNet

2. iOS Implementation

// AppDelegate.swift
import FirebaseAppCheck

class AppCheckProviderFactory: NSObject, AppCheckProviderFactory {
    func createProvider(with app: FirebaseApp) -> AppCheckProvider? {
        #if DEBUG
        // Use debug provider in development
        return AppCheckDebugProvider(app: app)
        #else
        // Use App Attest in production (iOS 14+)
        if #available(iOS 14.0, *) {
            return AppAttestProvider(app: app)
        }
        // Fallback to DeviceCheck for older iOS
        return DeviceCheckProvider(app: app)
        #endif
    }
}

// In application didFinishLaunchingWithOptions:
AppCheck.setAppCheckProviderFactory(AppCheckProviderFactory())

3. Android Implementation

// Application class
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        val providerFactory = if (BuildConfig.DEBUG) {
            DebugAppCheckProviderFactory.getInstance()
        } else {
            PlayIntegrityAppCheckProviderFactory.getInstance()
        }
        
        FirebaseAppCheck.getInstance().installAppCheckProviderFactory(providerFactory)
    }
}

4. Getting and Using the Token

// Kotlin example
suspend fun getAppCheckToken(): String? {
    return try {
        val tokenResult = FirebaseAppCheck.getInstance()
            .getAppCheckToken(false)
            .await()
        tokenResult.token
    } catch (e: Exception) {
        Log.e("AppCheck", "Failed to get token", e)
        null
    }
}

// Include in API calls
val token = getAppCheckToken()
val request = Request.Builder()
    .url("https://api.example.com/endpoint")
    .addHeader("X-Firebase-AppCheck", token)
    .build()

5. Token Storage Strategy

class AppCheckTokenManager(private val context: Context) {
    private val prefs = context.getSharedPreferences("appcheck", MODE_PRIVATE)
    
    companion object {
        private const val KEY_TOKEN = "appcheck_token"
        private const val KEY_EXPIRY = "appcheck_expiry"
        private const val TOKEN_VALIDITY_MS = 24 * 60 * 60 * 1000L // 24 hours
    }
    
    fun getCachedToken(): String? {
        val expiry = prefs.getLong(KEY_EXPIRY, 0)
        if (System.currentTimeMillis() > expiry) {
            return null // Token expired
        }
        return prefs.getString(KEY_TOKEN, null)
    }
    
    fun cacheToken(token: String) {
        prefs.edit()
            .putString(KEY_TOKEN, token)
            .putLong(KEY_EXPIRY, System.currentTimeMillis() + TOKEN_VALIDITY_MS)
            .apply()
    }
}

6. Backend Verification

// Node.js example
const admin = require('firebase-admin');

async function verifyAppCheckToken(req, res, next) {
    const appCheckToken = req.header('X-Firebase-AppCheck');
    
    if (!appCheckToken) {
        logToGrafana('missing_token', req);
        return res.status(401).json({ error: 'Missing App Check token' });
    }
    
    try {
        const appCheckClaims = await admin.appCheck().verifyToken(appCheckToken);
        req.appCheckVerified = true;
        req.appId = appCheckClaims.app_id;
        next();
    } catch (error) {
        logToGrafana('invalid_token', req, error);
        return res.status(401).json({ error: 'Invalid App Check token' });
    }
}

// Apply to protected routes
app.use('/api/v1/*', verifyAppCheckToken);

Debug Token Setup

During development, you need debug tokens to test without real device attestation.

Adding Debug Token in Firebase Console

  1. Go to Firebase Console → App Check
  2. Click on your app
  3. Go to the “Debug tokens” tab
  4. Click “Add debug token”
  5. Enter a descriptive name and copy the generated token

Using Debug Token in Development

// iOS - Set before Firebase configuration
let providerFactory = AppCheckDebugProviderFactory()
providerFactory.debugToken = "YOUR_DEBUG_TOKEN"
AppCheck.setAppCheckProviderFactory(providerFactory)
// Android - Set in gradle.properties or as environment variable
// Then in your debug build:
FirebaseAppCheck.getInstance().setTokenAutoRefreshEnabled(true)

Benefits

1. Enhanced Security

  • Protects APIs from unauthorized access
  • Prevents abuse from bots or malicious users
  • Ensures requests originate from your authenticated app

2. Simplified Integration

  • Integrates seamlessly with Firebase services
  • Requires minimal code changes
  • Works with custom APIs too

3. Token Expiry Management

  • Tokens are short-lived (24 hours)
  • Automatic refresh ensures continuous protection
  • Reduces risk of token misuse

4. Cost-Efficient Security

  • No need for complex third-party authentication
  • Leverages Firebase’s scalable infrastructure
  • Free tier covers most use cases

5. Backend Assurance

  • Backend can rely on Firebase’s verification
  • Reduces custom validation logic overhead
  • Clear audit trail of invalid requests

Monitoring and Alerting

Set up monitoring to detect abuse:

// Log suspicious activity to Grafana/Loki
function logToGrafana(eventType, req, error = null) {
    const logEntry = {
        timestamp: new Date().toISOString(),
        event: eventType,
        ip: req.ip,
        userAgent: req.headers['user-agent'],
        path: req.path,
        error: error?.message
    };
    
    // Send to your logging infrastructure
    logger.warn(logEntry);
}

Alerts to Configure

MetricThresholdAction
Missing tokens> 100/hourInvestigate source
Invalid tokens> 50/hourPossible attack
Token verification failures> 10/minCheck Firebase status

Best Practices

  1. Always verify on backend - Client-side checks can be bypassed
  2. Use debug tokens sparingly - Rotate them regularly
  3. Monitor invalid token rates - Spikes indicate attacks
  4. Plan for attestation failures - Have graceful degradation
  5. Keep Firebase SDK updated - Security patches are critical

Conclusion

Firebase App Check provides a robust layer of protection for your mobile APIs with minimal implementation effort. The key benefits:

  • Authenticity - Verify requests come from your real app
  • Simplicity - Drop-in solution with existing Firebase
  • Observability - Clear logging of suspicious activity

By implementing App Check, you create a secure communication channel between your app and backend, significantly reducing API abuse vectors.