Securing your Angular Application : JavaScript Obfuscation

Koye Mohan Reddy
6 min readDec 24, 2023

--

Obfuscation Example

Angular being primarily a client side application has it’s code exposed to user. With right set of skills one could reverse engineer your application or get underlying logic. To prevent this from happening it is better to obfuscate your code in the code of the framework to make it less clear and harder to understand.

Angular application exposing core details when inspecting( Just an example ).

When using out-of-the-box angular exposes :

  • Services
  • Components
  • Configurations

Inorder to safeguard out application we need to secure business logic we need to obfuscate our codebase to keep our data secure. However there are tools that help us in this scenario like uglifyjs which are useful to a certain extent, however obfuscation has better results.

Table Of Contents

· What is JavaScript Obfuscation ?
·
JavaScript Obfuscation Techniques :
·
JavaScript Obfuscation Example:
·
Installation and Setup
·
Updating build configuration :
·
Options provided by JavaScript Obfuscator :
·
Conclusion
·
What’s Next ?

What is JavaScript Obfuscation ?

JavaScript obfuscation involves a series of code transformations aimed at converting straightforward and easily readable JavaScript code into a modified version that is exceptionally challenging to comprehend and reverse-engineer.

JavaScript Obfuscation Techniques :

JavaScript obfuscation techniques are employed to make code more challenging to understand, reverse engineer, or tamper with. These techniques aim to obscure the code’s logic and structure while preserving its functionality. Here are some common JavaScript obfuscation techniques:

Variable Renaming:

Renaming variables to non-descriptive, short, or single-letter names. This makes it harder to understand the purpose of each variable.

Function Renaming:

Similar to variable renaming, functions can be renamed to obscure their intended functionality.

String Encryption:

Encrypting string literals in the code and decrypting them at runtime. This adds an extra layer of complexity for anyone trying to understand the code.

Code Splitting:

Breaking the code into multiple smaller functions or files and then dynamically loading or combining them. This makes the code structure less apparent.

Control Flow Obfuscation:

Introducing redundant or misleading control flow statements (such as extra loops or conditional statements) to confuse the code’s logic.

Dead Code Injection:

Injecting unused or irrelevant code snippets to create confusion. This makes it harder to distinguish between essential and non-essential parts of the code.

String Concatenation:

Breaking up strings and concatenating them dynamically at runtime. This makes it difficult to identify the actual strings used in the code.

Encoding and Decoding:

Encoding parts of the code and decoding them during execution. This includes using techniques like Base64 encoding.

Numeric Obfuscation:

Replacing numeric literals with mathematical expressions or other numeric representations to obfuscate numerical constants.

Anti-Debugging Techniques:

Incorporating code that detects if the application is being run in a debugger environment, and altering the code’s behavior accordingly.

Note: It’s important to note that while obfuscation can add a layer of protection, it’s not foolproof. Determined attackers may still reverse-engineer obfuscated code. Additionally, obfuscated code can be harder to maintain and debug. Developers should carefully consider the trade-offs and employ obfuscation judiciously, especially in scenarios where protecting intellectual property is a significant concern.

JavaScript Obfuscation Example:

Consider the following code below:

function getUserData() {
if(!user) {
return {};
}
let data = user.auth.providerData[0];
return {
name: data.displayName,
avatar: data.photoURL,
email: data.email,
provider: data.providerId,
uid: user.auth.uid
};
}
console.log(getUserData());

Uglified/Minified code :

function getUserData(){if(!user)return{};let e=user.auth.providerData[0];return{name:e.displayName,avatar:e.photoURL,email:e.email,provider:e.providerId,uid:user.auth.uid}}console.log(getUserData());

Here the code has been transformed upon closer inspection or using a formatter , one could figure out base logic of the code.

In another scenario after obfuscation :

(function(_0x49e7cc,_0x35d5f6){const _0xdfe59=_0x3b90,_0xd4b26e=_0x49e7cc();while(!![]){try{const _0x20c64f=parseInt(_0xdfe59(0xf9))/0x1*(-parseInt(_0xdfe59(0xfa))/0x2)+-parseInt(_0xdfe59(0xf7))/0x3*(parseInt(_0xdfe59(0xfc))/0x4)+parseInt(_0xdfe59(0xee))/0x5*(-parseInt(_0xdfe59(0xf1))/0x6)+parseInt(_0xdfe59(0xf5))/0x7+-parseInt(_0xdfe59(0xf4))/0x8+-parseInt(_0xdfe59(0xf2))/0x9+parseInt(_0xdfe59(0xf6))/0xa*(parseInt(_0xdfe59(0xf0))/0xb);if(_0x20c64f===_0x35d5f6)break;else _0xd4b26e['push'](_0xd4b26e['shift']());}catch(_0x29db07){_0xd4b26e['push'](_0xd4b26e['shift']());}}}(_0x53ef,0xd14a3));function getUserData(){const _0x2d7b7b=_0x3b90;if(!user)return{};let _0x4c86a3=user[_0x2d7b7b(0xef)]['providerData'][0x0];return{'name':_0x4c86a3['displayName'],'avatar':_0x4c86a3[_0x2d7b7b(0xfb)],'email':_0x4c86a3['email'],'provider':_0x4c86a3[_0x2d7b7b(0xf8)],'uid':user[_0x2d7b7b(0xef)][_0x2d7b7b(0xf3)]};}function _0x3b90(_0x40fb4f,_0x21bcc3){const _0x53ef2b=_0x53ef();return _0x3b90=function(_0x3b900f,_0x3ce21b){_0x3b900f=_0x3b900f-0xee;let _0x3732d2=_0x53ef2b[_0x3b900f];return _0x3732d2;},_0x3b90(_0x40fb4f,_0x21bcc3);}function _0x53ef(){const _0x2e2efd=['providerId','254438AtTuWZ','10GIuQhB','photoURL','4aVsWJI','20vNbtpg','auth','3729QVEYgK','340284BhPkpf','4165182zMXsCd','uid','13082696dHfPHS','10353784ybaJZp','98830ItJwbd','1125051BYspDY'];_0x53ef=function(){return _0x2e2efd;};return _0x53ef();}console['log'](getUserData());

Here the code become much more complex and harder to understand which is a much better outcome than our initial approach. In fact we can further increase the level of obfuscation which we will discuss further.

Now let’s go ahead with setting up our angular application for obfuscation.

Installation and Setup:

We would be using javascript-obfuscator & wepack-obfuscator plugin to obfuscate our code.

In your Angular Application install the above packages using the below command.

npm install --save-dev javascript-obfuscator webpack-obfuscator

If your angular application uses Webpack 4 use version 2.6.0 of the above packages as the latest versions are compatible with Webpack 5 only

Any angular app below 11 uses webpack 4.

Creating / Updating webpack Config :

Create a Wepack Config file incorporating the above plugin installed , if you are having an existing webpack config update it with following:

var WebpackObfuscator = require('webpack-obfuscator');
module.exports = {
module: {
...//Optional
},
// Webpack plugins array
plugins: [
new WebpackObfuscator ({
debugProtection: true
}, ['vendor.js'])
]
}

We are passing key/ option debugProtection as true which is one of the many options offered by javascript-obfuscator to control the outcome of obfuscation.

We would be configuring the build / dist of angular application thereby we would be updating angular.json to use custom-webpack builder to incorporate the above webpack.config which we have written.

Updating build configuration :

Install custom-webpack builder using the below command :

  • Install angular custom builder using the below command:
npm i @angular-builders/custom-webpack

You can read more about custom-webpack builder here (@angular-builders/custom-webpack).

  • Update your angular.json builder with the below code
{
...
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./extra-webpack.config.js",
"mergeStrategies": {
"module.rules": "prepend"
},
},
"outputPath": "dist",
...
}
}
}

With this we can build our application to generate obfuscated code of our application.

Generating Obfuscated Build :

Simply build your application using angular cli options :

ng build

Update node_options for larger builds.

After build we can observe that the key which got exposed in our application ( first image from top) is no longer visible.

After Obfuscation

Options provided by JavaScript Obfuscator :

In our above example we have passed debugProtection as true , it results in preventing debugging in our application by injecting an anonymous function with debugger enabled.

Debug Protection Enabled

Few key Options offered :

  • stringArray :
    Removes string literals and place them in a special array. For instance, the string "Hello World" in var m = "Hello World"; will be replaced with something like var m = _0x13a678[0x2];
  • stringArrayThreshold :
    You can use this setting to adjust the probability (from 0 to 1) that a string literal will be inserted into the stringArray.Defaulted to 0.8 ( Keeping at 1 results in code breaking at some stages)
  • debugProtection:
    Enables debugging protection as seen above. Interval at which debugging protection in enabled (in mili seconds ).
  • transformObjectKeys :
    Decide whether transformation should be applied to object keys or not.(default : false).
  • forceTransformStrings :
    Since stringLiterals addition is based on a probability you can control certain keys which will be forcefully added into string array. It accepts and array of regex expressions(Use escape characters when using strings with special characters).
  • stringArrayEncoding :
    Encodes string literals using base64 or rc4, accepts array. On below configstringArray value won't be encoded, and some values will be encoded with base64 and rc4 encoding:
stringArrayEncoding: [
'none',
'base64',
'rc4'
]

These are few of the popular keys which can be useful , you can read more about it here & try out here.

In case you are having code issues ( getAttribute of undefined , tap of undefined or prototype undefined — to name a few) try using the below config:

new WebpackObfuscator({
compact: true,
controlFlowFlattening: false,
deadCodeInjection: false,
debugProtection: false,
identifierNamesGenerator: 'hexadecimal',
numbersToExpressions: false,
renameGlobals: false,
selfDefending: false,
simplify: false,
splitStrings: false,
stringArray: true,
transformObjectKeys: false,
stringArrayCallsTransform: false,
stringArrayEncoding: [],
stringArrayIndexShift: false,
stringArrayRotate: false,
stringArrayShuffle: false,
stringArrayWrappersCount: 0,
stringArrayWrappersChainedCalls: false,
stringArrayWrappersParametersMaxCount: 2,
stringArrayWrappersType: 'variable',
stringArrayThreshold: 1,
unicodeEscapeSequence: false,
renamePropertiesMode: 'safe',
renameProperties: false
},['vendor.js']),

Have included vendor.js for angular framework related errors ( optional ) few of the default options have been reinstated and can be removed.

Conclusion

We have obfuscted our angular codebase making it one step closer to a more secure code. Even though we have implemented in angular it is not limited to the same and can be implemented in other frameworks like react as well.

What’s Next ?

Encryption : Encrypting Network request using AES & RSA keeping our data confidential.

Connect with me : https://www.linkedin.com/in/kmohan-reddy/

References :

https://www.linkedin.com/pulse/protect-source-code-your-angular-applications-karim-reda-fakhir/

--

--

Koye Mohan Reddy
Koye Mohan Reddy

Written by Koye Mohan Reddy

I'm a Frontend Developer at Faclon Labs. Angular | Angular Material | Typescript | JavaScript | Linkedin : https://www.linkedin.com/in/kmohan-reddy/

Responses (1)