What is onNewIntent, issues with it and how to use it properly without sacrificing security?
I've been working on Android development since 2021, and one method that caught my eye these days was onNewIntent()
.
Let me explain what it is, why it can be dangerous, and how to use it safely.
What is onNewIntent?
onNewIntent()
gets called when your activity is already running and a new intent tries to start it. Instead of creating a new instance, Android delivers the new intent to your existing activity through this method.
Here's a basic example:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
handleIntent(intent)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let { handleIntent(it) }
}
private fun handleIntent(intent: Intent) {
// Process the intent data
val data = intent.getStringExtra("user_data")
// Do something with the data
}
}
This happens when your activity has launch modes like singleTop
, singleTask
, or singleInstance
in the manifest.
The Security Problem
Here's where things get tricky. Other apps can send intents to your activity if it's exported or has intent filters. And if you're not careful, they can pass malicious data.
I've seen developers do this:
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
// DON'T DO THIS - Direct access without validation
val userId = intent?.getStringExtra("user_id")
val accountData = intent?.getParcelableExtra<Account>("account")
// Directly using the data without checks
loadUserProfile(userId!!)
processAccount(accountData!!)
}
This is dangerous because any app can craft an intent with malicious data and send it to your activity.
Common Attack Scenarios
Data Injection: Malicious apps can pass harmful strings that might break your app or cause crashes.
Intent Spoofing: Apps can pretend to be legitimate sources and pass fake data.
State Manipulation: Attackers might try to manipulate your app's state by sending unexpected intents.
How to Use onNewIntent Safely
Here's how I handle onNewIntent()
securely:
class SecureActivity : AppCompatActivity() {
private companion object {
const val EXPECTED_ACTION = "com.yourapp.SECURE_ACTION"
const val TRUSTED_PACKAGE = "com.yourapp.trusted"
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let {
if (isIntentSafe(it)) {
handleSecureIntent(it)
} else {
Log.w("SecureActivity", "Rejected unsafe intent")
}
}
}
private fun isIntentSafe(intent: Intent): Boolean {
// Check the action
if (intent.action != EXPECTED_ACTION) {
return false
}
// Verify the calling package if needed
val callingPackage = callingActivity?.packageName
if (callingPackage != null && callingPackage != TRUSTED_PACKAGE) {
return false
}
// Validate data types and ranges
val userId = intent.getStringExtra("user_id")
if (userId.isNullOrBlank() || !isValidUserId(userId)) {
return false
}
return true
}
private fun handleSecureIntent(intent: Intent) {
val userId = intent.getStringExtra("user_id")
// Safe to use now
userId?.let { loadUserProfile(it) }
}
private fun isValidUserId(userId: String): Boolean {
// Your validation logic here
return userId.matches(Regex("^[a-zA-Z0-9_-]+$")) && userId.length <= 50
}
}
Best Practices
Always validate: Never trust data from intents without validation.
Use explicit intents: When possible, use explicit intents instead of implicit ones.
Limit exported activities: Only export activities that really need to be accessible from other apps.
// In AndroidManifest.xml
<activity
android:name=".MainActivity"
android:exported="false" // This prevents other apps from starting it
android:launchMode="singleTop" />
Check calling activity: Use callingActivity
to verify who's sending the intent.
Sanitize data: Clean and validate all incoming data before processing.
When to Use Different Launch Modes
-
singleTop
: Use when you want to avoid creating duplicate activities on top of the stack -
singleTask
: Use for main activities that should have only one instance -
singleInstance
: Use rarely, only for special cases like phone dialer
Each of these will trigger onNewIntent()
under certain conditions.
Real-World Example
I once worked on a banking app where we had to handle deep links. The initial implementation was vulnerable because it trusted all incoming intents. We fixed it by:
- Validating the deep link format
- Checking if the user was authenticated
- Sanitizing all parameters
- Using a whitelist of allowed actions
private fun handleDeepLink(intent: Intent) {
val uri = intent.data ?: return
// Validate the URI scheme and host
if (uri.scheme != "bankapp" || uri.host != "secure") {
return
}
// Check authentication first
if (!userManager.isAuthenticated()) {
redirectToLogin()
return
}
// Process only allowed paths
when (uri.path) {
"/transfer" -> handleTransfer(uri)
"/balance" -> showBalance()
else -> showError("Invalid deep link")
}
}
Takeaway
onNewIntent()
is useful but comes with security risks. Always validate incoming data, limit activity exposure, and never trust external intents blindly.
The key is treating every intent as potentially malicious until proven otherwise. It might seem paranoid, but it's better than dealing with security vulnerabilities later.
And remember, security isn't just about preventing crashes. It's about protecting your users' data and maintaining trust in your app.
Top comments (0)