Is your Android app DDoSing your backend?

Does your Android app receive push notifications via Firebase Messaging? Does it call your backend inside the <inline-code>Application<inline-code>'s <inline-code>onCreate<inline-code> method? If you answered yes to both of these questions, you might be facing a silent, lurking problem.

Why?

Sure, adding some initialization code at  the point where the app starts is a valid use case and a well-tested pattern. You might want to download a fresh state from the backend on each cold start. What could possibly go wrong? Naturally, you put something like this in the <inline-code>onCreate<inline-code> method of your <inline-code>Application<inline-code>:

override fun onCreate() {
    super.onCreate()
    
    scope.launch {
        downloadAndCacheUserData()
    }
}

This is clear and straightforward – you <inline-code>launch<inline-code> a coroutine, which then performs some initialization logic.

This works as expected – when a user cold-starts your app, this logic is performed and the latest user data are fetched.

But, when your app supports receiving notifications via Firebase Messaging, this approach could cause your backend to be overwhelmed, leading to the Denial of Service.

Looking under the hood

Is your app DDoSing your backend? 

The Firebase Messaging minimal setup contains only the dependency definition (<inline-code>"com.google.firebase:firebase-messaging"<inline-code>). This alone (provided you’ve already included <inline-code>googleservices.json<inline-code>) is enough for your app to start receiving notifications, because the library’s <inline-code>Manifest.xml<inline-code> includes the definition for the default <inline-code>FirebaseMessagingService<inline-code> with the low <inline-code>intent-filter<inline-code> 's priority:

<service
    android:name="com.google.firebase.messaging.FirebaseMessagingService"
    android:directBootAware="true"
    android:exported="false" >
    <intent-filter android:priority="-500" >
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

Going further through the library’s <inline-code>Manifest.xml<inline-code>, we can see that there’s another important definition:

<receiver
    android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
    android:exported="true"
    android:permission="com.google.android.c2dm.permission.SEND" >
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
    </intent-filter>
    <meta-data
        android:name="com.google.android.gms.cloudmessaging.FINISHED_AFTER_HANDLED"
        android:value="true" />
</receiver>

This code defines an exported <inline-code>BroadcastReceiver<inline-code> that’s responsible for actually receiving push notification broadcasts from Google Play services. There, in the overridden <inline-code>onMessageReceived<inline-code>, the receiver starts or binds the <inline-code>MessagingService<inline-code> we described earlier and passes the <inline-code>Intent<inline-code> with the notification data to it.

Then, depending on several criteria, <inline-code>FirebaseMessagingService<inline-code> handles the intent and decides what to do with the message. When delivering the most basic notifications (without the <inline-code>data<inline-code> payload), the <inline-code>FirebaseMessagingService<inline-code> distinguishes between two situations:

  • the app is not in the foreground - the service posts the notification to the system’s notification tray, or 
  • the app is in the foreground - the service doesn’t post the notification, but instead it calls the <inline-code>onMessageReceived<inline-code> function on the implementing class (if any). In any case, for the service to be able to process any notification, it first has to be started. 

The default Messaging service lives in your app’s process. So, if the app isn’t running, the system first needs to start your app. This is pretty standard Android behavior, as no component that is part of your app can run unless the application is started first.

The problem

You might already see where this is going. When your app performs API calls on your app’s startup, these API calls are triggered even when your app was started only in order to deliver the push notification. This behavior can be potentially dangerous, as it can generate an enormous number of requests (depending on the number of users) to your backend, which can eventually lead to the Denial of Service or even increased cloud-provider costs (e.g. if you have a serverless backend).

The solution

First of all, you need to analyze the requests you’re performing on your app’s startup. If you’re only calling the initialization of some 3rd party service (e.g. crash reporting or log processing), you’re probably fine, as this is usually a part of the recommended setup.

On the other hand, if the requests you’re making are calling your API, you should ask yourself “Does this request need to be run even if the user isn’t actually interacting with my app?” Depending on the answer, here are the possible solutions: If the request needs to be made only when the user interacts with the app (e.g. downloading the newest data), you can either postpone the request until <inline-code>Activity.onCreate<inline-code>, or you can utilize <inline-code>ProcessLifecycleOwner<inline-code> to execute the given code only when your app actually comes to the foreground and the (<inline-code>ON_START)<inline-code> lifecycle event is triggered.

If the request really needs to be executed each time the app is launched, even just in the background, you could prepare your backend to handle a potentially large number of parallel requests by incorporating a load balancer, which would distribute requests between multiple instances of your backend.

Another much less scalable solution would be to adjust the way you send out notifications. You could divide your recipients into groups and send notifications to only a portion of recipients at a time. As mentioned, however, this solution doesn’t provide enough scalability.

Is your Android app DDoSing your backend?
Maroš Šeleng
Android Developer
By clicking “Accept”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.