App security best practices

By making your app more secure, you help preserve user trust and device
integrity.

This page presents several best practices that have a significant, positive
impact on your app's security.

Enforce secure communication

When you safeguard the data that you exchange between your app and other
apps, or between your app and a website, you improve your app's stability and
protect the data that you send and receive.

Use implicit intents and non-exported content providers

Show an app chooser

If an implicit intent can launch at least two possible apps on a user's
device, explicitly show an app chooser. This interaction strategy allows users
to transfer sensitive information to an app that they trust.

Kotlin

val intent = Intent(ACTION_SEND)
val possibleActivitiesList: List<ResolveInfo> =
queryIntentActivities(intent, PackageManager.MATCH_ALL)
// Verify that an activity in at least two apps on the user's device
// can handle the intent. Otherwise, start the intent only if an app
// on the user's device can handle the intent.
if (possibleActivitiesList.size > 1) {
// Create intent to show chooser.
// Title is something similar to "Share this photo with".
val chooser = resources.getString(R.string.chooser_title).let { title ->
Intent.createChooser(intent, title)
}
startActivity(chooser)
} else if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
}

Java

Intent intent = new Intent(Intent.ACTION_SEND);
List<ResolveInfo> possibleActivitiesList =
queryIntentActivities(intent, PackageManager.MATCH_ALL);
// Verify that an activity in at least two apps on the user's device
// can handle the intent. Otherwise, start the intent only if an app
// on the user's device can handle the intent.
if (possibleActivitiesList.size() > 1) {
// Create intent to show chooser.
// Title is something similar to "Share this photo with".
String title = getResources().getString(R.string.chooser_title);
Intent chooser = Intent.createChooser(intent, title);
startActivity(chooser);
} else if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}

Apply signature-based permissions

When sharing data between two apps that you control or own, use
signature-based permissions. These permissions don't require user
confirmation and instead check that the apps accessing the data are signed using
the same signing key. Therefore, these permissions offer a more streamlined,
secure user experience.

Disallow access to your app's content providers

Unless you intend to send data from your app to a different app that you
don't own, you should explicitly disallow other developers' apps from accessing
the ContentProvider objects that your app contains. This
setting is particularly important if your app can be installed on devices
running Android 4.1.1 (API level 16) or lower, as the
android:exported
attribute of the
<provider>
element is true by default on those versions of Android.

Ask for credentials before showing sensitive information

When requesting credentials from users so that they can access sensitive
information or premium content in your app, ask for either a
PIN/password/pattern or a biometric credential, such as using face recognition
or fingerprint recognition.

During the development process, you can use the
<debug-overrides> element to expliticly allow
user-installed certificates. This element overrides your app's
security-critical options during debugging and testing without affecting the
app's release configuration. The following snippet shows how to define this
element in your app's network security configuration XML file:

Use HTML message channels

If your app must use JavaScript interface support on devices running Android
6.0 (API level 23) and higher, use HTML message channels instead of
evaluateJavascript() to
communicate between a website and your app, as shown in the following code
snippet:

Provide the right permissions

Your app should request only the minimum number of permissions necessary to
function properly. When possible, your app should relinquish some of these
permissions when they're no longer needed.

Use intents to defer permissions

Whenever possible, don't add a permission to your app to complete an action
that could be completed in another app. Instead, use an intent to defer the
request to a different app that already has the necessary permission.

The following example shows how to use an intent to direct users to a
contacts app instead of requesting the
READ_CONTACTS and
WRITE_CONTACTS permissions:

Kotlin

// Delegates the responsibility of creating the contact to a contacts app,
// which has already been granted the appropriate WRITE_CONTACTS permission.
Intent(Intent.ACTION_INSERT).apply {
type = ContactsContract.Contacts.CONTENT_TYPE
}.also { intent ->
// Make sure that the user has a contacts app installed on their device.
intent.resolveActivity(packageManager)?.run {
startActivity(intent)
}
}

Java

// Delegates the responsibility of creating the contact to a contacts app,
// which has already been granted the appropriate WRITE_CONTACTS permission.
Intent insertContactIntent = new Intent(Intent.ACTION_INSERT);
insertContactIntent.setType(ContactsContract.Contacts.CONTENT_TYPE);
// Make sure that the user has a contacts app installed on their device.
if (insertContactIntent.resolveActivity(getPackageManager()) != null) {
startActivity(insertContactIntent);
}

In addition, if your app needs to perform file-based I/O—such as
accessing storage or choosing a file—it doesn't need special permissions
because the system can complete the operations on your app's behalf. Better
still, after a user selects content at a particular URI, the calling app gets
granted permission to the selected resource.

When sharing data, use "content://" URIs, not "file://" URIs. Instances of
FileProvider do this for you.

The following code snippet shows how to use URI permission grant flags and
content provider permissions to display an app's PDF file in a separate PDF
Viewer app:

Kotlin

// Create an Intent to launch a PDF viewer for a file owned by this app.
Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("content://com.example/personal-info.pdf")
// This flag gives the started app read access to the file.
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}.also { intent ->
// Make sure that the user has a PDF viewer app installed on their device.
intent.resolveActivity(packageManager)?.run {
startActivity(intent)
}
}

Java

// Create an Intent to launch a PDF viewer for a file owned by this app.
Intent viewPdfIntent = new Intent(Intent.ACTION_VIEW);
viewPdfIntent.setData(Uri.parse("content://com.example/personal-info.pdf"));
// This flag gives the started app read access to the file.
viewPdfIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// Make sure that the user has a PDF viewer app installed on their device.
if (viewPdfIntent.resolveActivity(getPackageManager()) != null) {
startActivity(viewPdfIntent);
}

Note: Untrusted apps that target Android 10 (API level 29) and higher can't invoke
exec() on files within the app's home directory. This execution of files from
the writable app home directory is a
W^X violation. Apps should
load only the binary code that's embedded within an app's APK file.
In addition, apps that target Android 10 and higher cannot in-memory modify
executable code from files which have been opened with dlopen(). This includes any
shared object (.so) files with text relocations.

Store data safely

Although your app might require access to sensitive user information, your
users will grant your app access to their data only if they trust that you'll
safeguard it properly.

Store private data within internal storage

Store all private user data within the device's internal storage, which is
sandboxed per app. Your app doesn't need to request permission to view these
files, and other apps cannot access the files. As an added security measure,
when the user uninstalls an app, the device deletes all files that the app saved
within internal storage.

The following code snippet demonstrates one way to write data to internal
storage:

Use external storage cautiously

By default, the Android system doesn't enforce security restrictions on data
that resides within external storage, and the storage medium itself isn't
guaranteed to stay connected to the device. Therefore, you should apply the
following security measures to provide safe access to information within
external storage.

Use scoped directory access

If your app needs to access only a specific directory within the device's
external storage, you can use
scoped directory
access to limit your app's access to a device's external storage
accordingly. As a convenience to users, your app should save the directory
access URI so that users don't need to approve access to the directory every
time your app attempts to access it.

Note: If you use scoped directory access with a
particular directory in external storage, know that the user might eject the
media containing this storage while your app is running. You should include
logic to gracefully handle the change to the
Environment.getExternalStorageState()
return value that this user behavior causes.

The following code snippet uses scoped directory access with the pictures
directory within a device's primary shared storage:

Check validity of data

If your app uses data from external storage, make sure that the contents of
the data haven't been corrupted or modified. Your app should also include logic
to handle files that are no longer in a stable format.

The following example shows the permission and logic that check a file's
validity:

Store only non-sensitive data in cache files

To provide quicker access to non-sensitive app data, store it in the device's
cache. For caches larger than 1 MB in size, use
getExternalCacheDir();
otherwise, use getCacheDir(). Each
method provides you with the File object that contains your
app's cached data.

The following code snippet shows how to cache a file that your app recently
downloaded:

Java

Note: If you use
getExternalCacheDir() to
place your app's cache within shared storage, the user might eject the media
containing this storage while your app is running. You should include logic to
gracefully handle the cache miss that this user behavior causes.

Caution: There is no security enforced on these files.
Therefore, any app that has the
WRITE_EXTERNAL_STORAGE permission can access
the contents of this cache.

Check the Google Play services security provider

If your app uses Google Play services, make sure that it's updated on the
device where your app is installed. This check should be done asynchronously,
off of the UI thread. If the device isn't up-to-date, your app should trigger an
authorization error.