Your Current Security Measures
Currently, I have the following security measures.
- JWT Token using PassportJS per request after login
Bear in mind that the JWT token only identifies who is in the request, not what is doing the request.
- Rate limiting on just my Login and Register endpoints at the moment
Congratulations by having this measure in place, but you should extend it to all endpoints and adjust the rate limit in a per endpoint basis. Try to figure out the
normal rate per second/minute for a normal usage and set the rate limit just a little above it, and have monitoring in place to see when you have spikes in requests being blocked due to exceeding the rate limit.
- Emailing a user that someone unknown has logged in from a different device
That's a measure that people do not talk about often or even implement, but it's a very important one to ensure your users security. You can enhance it by only allowing that new login happens from a different device by sending an approoval link to the user email.
The Difference Between WHO and WHAT is Accessing the API Server
Before I dive into your question What else could I be doing to secure the backend?
I would like to first clear a misconception that usually I find among developers of any seniority, that is about the difference between who and what is accessing an API server.
I wrote a series of articles around API and Mobile security, and in the article Why Does Your Mobile App Need An Api Key? you can read in detail the difference between who and what is accessing your API server, but I will extract here the main takes from it:
The what is the thing making the request to the API server. Is it really a genuine instance of your mobile app, or is it a bot, an automated script or an attacker manually poking around your API server with a tool like Postman?
The who is the user of the mobile app that we can authenticate, authorize and identify in several ways, like using OpenID Connect or OAUTH2 flows.
So think about the who as the user your API server will be able to Authenticate and Authorize access to the data, and think about the what as the software making that request in behalf of the user you have authenticated with the PassportJS JWT token.
Other Security Measures
What else could I be doing to secure the backend?
As you may be aware by now you are lacking measures to allow your backend to attest what is doing the request, because your current measures are centered around the who in the request, that is represented by the PassportJS JWT token.
In order to allow the backend to have more confidence that is indeed receiving requests from what it expects, your mobile app, you need to approach this from both sides, the mobile app and backend.
HMAC - Digitally Sign Requests
So you can start by digitally sign the requests the mobile app does to the backend, and you can see here a simple approach of doing it so in the mobile app:
private fun calculateAPIRequestHMAC(url: URL, authHeaderValue: String): String {
val secret = JniEnv().getHmacSecret()
var keySpec: SecretKeySpec
// Configure the request HMAC based on the demo stage
when (currentDemoStage) {
DemoStage.API_KEY_PROTECTION, DemoStage.APPROOV_APP_AUTH_PROTECTION -> {
throw IllegalStateException("calculateAPIRequestHMAC() not used in this demo stage")
}
DemoStage.HMAC_STATIC_SECRET_PROTECTION -> {
// Just use the static secret to initialise the key spec for this demo stage
keySpec = SecretKeySpec(Base64.decode(secret, Base64.DEFAULT), "HmacSHA256")
Log.i(TAG, "CALCULATE STATIC HMAC")
}
DemoStage.HMAC_DYNAMIC_SECRET_PROTECTION -> {
Log.i(TAG, "CALCULATE DYNAMIC HMAC")
// Obfuscate the static secret to produce a dynamic secret to initialise the key
// spec for this demo stage
val obfuscatedSecretData = Base64.decode(secret, Base64.DEFAULT)
val shipFastAPIKeyData = loadShipFastAPIKey().toByteArray(Charsets.UTF_8)
for (i in 0 until minOf(obfuscatedSecretData.size, shipFastAPIKeyData.size)) {
obfuscatedSecretData[i] = (obfuscatedSecretData[i].toInt() xor shipFastAPIKeyData[i].toInt()).toByte()
}
val obfuscatedSecret = Base64.encode(obfuscatedSecretData, Base64.DEFAULT)
keySpec = SecretKeySpec(Base64.decode(obfuscatedSecret, Base64.DEFAULT), "HmacSHA256")
}
}
//Log.i(TAG, "protocol: ${url.protocol}")
//Log.i(TAG, "host: ${url.host}")
//Log.i(TAG, "path: ${url.path}")
//Log.i(TAG, "Authentication: $authHeaderValue")
// Compute the request HMAC using the HMAC SHA-256 algorithm
val hmac = Mac.getInstance("HmacSHA256")
hmac.init(keySpec)
hmac.update(url.protocol.toByteArray(Charsets.UTF_8))
hmac.update(url.host.toByteArray(Charsets.UTF_8))
hmac.update(url.path.toByteArray(Charsets.UTF_8))
hmac.update(authHeaderValue.toByteArray(Charsets.UTF_8))
return hmac.doFinal().toHex()
}
The backend verification for the dynamic HMAC is done here:
router.use(function(req, res, next) {
log.info('---> VALIDATING DYNAMIC HMAC <---')
let base64_decoded_hmac_secret = Buffer.from(config.SHIPFAST_API_HMAC_SECRET, 'base64')
// Obfuscate the static secret to produce a dynamic secret to use during HMAC
// verification for this demo stage
let obfuscatedSecretData = base64_decoded_hmac_secret
let shipFastAPIKeyData = new Buffer(config.SHIPFAST_API_KEY)
for (let i = 0; i < Math.min(obfuscatedSecretData.length, shipFastAPIKeyData.length); i++) {
obfuscatedSecretData[i] ^= shipFastAPIKeyData[i]
}
let obfuscatedSecret = new Buffer(obfuscatedSecretData).toString('base64')
hmac = crypto.createHmac('sha256', Buffer.from(obfuscatedSecret, 'base64'))
if (hmacHelpers.isValidHmac(hmac, config, req)) {
next()
return
}
res.status(400).send()
return
})
But while this is a good step in the right direction it can be bypassed by reverse engineering the mobile app with a tool like Frida:
Inject your own scripts into black box processes. Hook any function, spy on crypto APIs or trace private application code, no source code needed. Edit, hit save, and instantly see the results. All without compilation steps or program restarts.
This type of situation can be mitigated by using the Mobile App Attestation concept, and to learn more about it I recommend you to read this answer I gave to the question How to secure an API REST for mobile app?, specially on the sections for Securing the API Server and A Possible Better Solution.
Do you Want to go the Extra Mile?
In any response to a security question I always like to reference the awesome work from the OWASP foundation.
For APIS
OWASP API Security Top 10
The OWASP API Security Project seeks to provide value to software developers and security assessors by underscoring the potential risks in insecure APIs, and illustrating how these risks may be mitigated. In order to facilitate this goal, the OWASP API Security Project will create and maintain a Top 10 API Security Risks document, as well as a documentation portal for best practices when creating or assessing APIs.
For Mobile Apps
OWASP Mobile Security Project - Top 10 risks
The OWASP Mobile Security Project is a centralized resource intended to give developers and security teams the resources they need to build and maintain secure mobile applications. Through the project, our goal is to classify mobile security risks and provide developmental controls to reduce their impact or likelihood of exploitation.
OWASP - Mobile Security Testing Guide:
The Mobile Security Testing Guide (MSTG) is a comprehensive manual for mobile app security development, testing and reverse engineering.