How To Protect Your Angular.js Application With Jscrambler

By Júlia Rocha 6 min read

Are you seeking solutions to ensure your Angular.js app is safe? See how to protect your Angular.js Application with Jscrambler.

Although AngularJs, Angular’s first version, is a few years old, it is still responsible for powering a lot of enterprise applications. It helps you build solid MVC applications by leveraging dependency injection and single-responsibility patterns.

There are a lot of tutorials and resources out there to help you get started with Angular, but it is usually better to start with the official documentation.

Today, we’ll go through the steps to protect your AngularJS application, including setup, the build process, and compatibility with Jscrambler.

Integrating Jscrambler with an AngularJS app

We’re using the Real World example app. Now, clone the app and install dependencies:

git clone https://github.com/JscramblerBlog/angularjs-realworld-example-app.git
cd angularjs-realworld-example-app
npm install

Jscrambler Configuration

If you haven’t created a Jscrambler account yet, be sure to do so before moving forward.

The first step to protect our application is setting up our Jscrambler configuration. If you haven’t used Jscrambler before, please consider reading the Code Integrity Getting Started guide, which will walk you through the steps to protect your app.

Reading the guide will teach you the core concepts of how to interact with Jscrambler and will make some parts of this article easier to reason about.

To protect our application, we’ll use the following configuration, based on Jscrambler’s Obfuscation default template. Create a .jscramblerrc file at the root of your project and paste your configuration.

{
  "keys": {
    "accessKey": "<YOUR_ACCESS_KEY>",
    "secretKey": "<YOUR_API_KEY>"
  },
  "applicationId": "<YOUR_APP_ID>",
  "params": [
    {
      "name": "whitespaceRemoval"
    },
    {
      "name": "identifiersRenaming",
      "options": {
        "mode": "SAFEST"
      }
    },
    {
      "name": "dotToBracketNotation"
    },
    {
      "name": "deadCodeInjection"
    },
    {
      "name": "stringConcealing"
    },
    {
      "name": "functionReordering"
    },
    {
      "name": "functionOutlining",
      "options": {
        "freq": 1,
        "features": [
          "opaqueFunctions"
        ]
      }
    },
    {
      "name": "propertyKeysObfuscation"
    },
    {
      "name": "regexObfuscation"
    },
    {
      "name": "booleanToAnything"
    }
  ],
  "areSubscribersOrdered": false,
  "applicationTypes": {
    "webBrowserApp": true,
    "desktopApp": false,
    "serverApp": false,
    "hybridMobileApp": false,
    "javascriptNativeApp": false,
    "html5GameApp": false
  },
  "languageSpecifications": {
    "es5": true,
    "es6": false,
    "es7": false
  },
  "useRecommendedOrder": true,
  "jscramblerVersion": "<JSCRAMLBER_VERSION>",
  "sourceMaps": false
}

Don’t forget to specify which Jscrambler version you want to use, along with your application ID and API keys.

Protecting Our App

You should be able to verify that our app has a gulpfile.js, which handles the build process. Jscrambler offers a Gulp plugin that we can use to integrate Jscrambler into our app’s build process.

You can install the plugin by running the following command:

npm install --save-dev gulp-jscrambler

Upon inspection, we can notice that our package.json file doesn’t include any scripts. npm scripts are an easy way to interact with our application, so let’s add two scripts, dev and build, to run our app in development mode and build our production assets, respectively.

"scripts": {
  "dev": "gulp",
  "build": "gulp build"
}

The dev script will run our default gulp task, which builds the app and watches for changes. The build script builds our production assets and places them in the dist directory.

To protect our application, we need to make some changes to our gulpfile.

We can observe that our build task pipes the contents of the build directory, uglifies them, and places them in the dist directory.

Since Jscrambler already offers minification features in the form of the identifiersRenaming and whitespaceRemoval transformations, we don’t need to uglify our main.js file.

So we can replace the call to the uglify gulp plugin with the Jscrambler plugin and remove the UglifyJS plugin from our project’s dependencies.

//...
// var uglify = require('gulp-uglify'); <- Remove
var jscrambler = require('gulp-jscrambler');
//...
gulp.task("build", ["html", "browserify"], function() {
  var html = gulp.src("build/index.html").pipe(gulp.dest("./dist/"));
  var js = gulp
    .src("build/main.js")
    //.pipe(uglify()) <- Remove
    .pipe(jscrambler())
    .pipe(gulp.dest("./dist/"));
  return merge(html, js);
});

We don’t need to specify any plugin parameters since it will use the .jscramblerrc file. The .jscramblerrc file shouldn’t be committed to your repository since it contains your Jscrambler API keys and application ID.

You can, however, leave the keys and the application ID in the .jscramblerrc file and move the Jscrambler parameterization to the gulpfile, so that everyone who clones the repo has access to the same protection configuration:

gulp.task("build", ["html", "browserify"], function() {
  var html = gulp.src("build/index.html").pipe(gulp.dest("./dist/"));
  var js = gulp
    .src("build/main.js")
    .pipe(
      jscrambler({
        params: [
          {
            name: "dotToBracketNotation"
          },
          {
            name: "stringConcealing"
          },
          {
            name: "functionReordering"
          },
          {
            name: "identifiersRenaming",
            options: {
              mode: "SAFEST"
            }
          },
          {
            name: "deadCodeInjection"
          },
          {
            name: "functionOutlining",
            options: {
              freq: 1,
              features: [
                "opaqueFunctions"
              ]
            }
          },
          {
            name: "propertyKeysObfuscation"
          },
          {
            name: "regexObfuscation"
          },
          {
            name: "booleanToAnything"
          }
        ],
        areSubscribersOrdered: false,
        applicationTypes: {
          webBrowserApp: true,
          desktopApp: false,
          serverApp: false,
          hybridMobileApp: false,
          javascriptNativeApp: false,
          html5GameApp: false
        },
        languageSpecifications: {
          es5: true,
          es6: false,
          es7: false
        },
        useRecommendedOrder: true,
        jscramblerVersion: "5.1",
        sourceMaps: false
      })
    )
    .pipe(gulp.dest("./dist/"));
  return merge(html, js);
});

Since Jscrambler reproduces the input directory structure, it will place the build/main.js file at dist/build/main.js. Our index.html file requires dist/main.js, so our application will be broken.

Here, we have two options: either we also protect our index.html file, which currently has no JavaScript, or we move the contents to their correct location after the protection. We can do this either in the gulpfile or in the npm script. For simplicity’s sake, let’s do it in the build script.

"build": "gulp build && mv dist/build/main.js dist/ && rm -r dist/build"

Our protected JavaScript will be inside dist/main.js.

To run our application, we need an http-server. Let’s install a package that lets us create one and add a script to run our application. Our npm scripts should look like this:

"scripts": {
  "dev": "gulp",
  "build": "gulp build && mv dist/build/main.js dist/ && rm -r dist/build",
  "start": "cd dist/ && http-server -o"
}

Then we just need to run the following commands, and we should be able to see our protected app working:

npm i --save-dev http-server
npm start

Known Compatibility Issues

Identifiers Renaming

Your AngularJS app may break when using the identifiersRenaming transformation since the transformation will rename some of the identifiers that begin with the $ character, such as $scope or $stateProvider. To work around this issue, we have two options.

Either we exclude all of those names from being renamed by using the excludeList option:

'params': [  
  {
    "name": "identifiersRenaming",
    "options": {
      "mode": "SAFEST",
      "excludeList": [
        "$*"
      ]
    }
  }
]

Or we modify our controllers to inject the dependencies in an alternative fashion, which will not break the framework’s dependency injection system:

// Before
angular.module('starter.controllers', [])
  .controller('ChatsCtrl', function($scope, Chats) {
    $scope.chats = Chats.all();
    $scope.remove = function(chat) {
      Chats.remove(chat);
    };
  });
// After
angular.module('starter.controllers', []);
// Create a function for the controller
function ChatsCtrl($scope, Chats){  
  $scope.chats = Chats.all();
  $scope.remove = function(chat) {
    Chats.remove(chat);
  };
}
// Create a $inject property with the dependencies
ChatsCtrl.$inject = ['$scope', 'Chats'];
// Assign the controller to the module
angular.module('starter.controllers').controller('ChatsCtrl', ChatsCtrl);  

By doing this refactor, we don’t need to use an excludeList.

Function Reordering and Self Defending

Jscrambler’s selfDefending transformation converts function declarations into variable declarations. The value of the newly created variable is a function expression:

// Function declaration
function foo() {  
    return 'foo!';
}
// Variable with a function expression
var bar = function() {  
    return 'bar!';
}

The usage of both selfDefending and functionReordering may lead to cases where we are assigning our controllers’ dependencies before we have actually declared it:

// Protected code resulting of applying selfDefending with functionReordering
// Will break the app
// Even though the ChatsCtrl variable is declared (hoisted)
// it's not assigned to an object
// Results in the error: Unable to set property $inject of undefined
ChatsCtrl.$inject = ['$scope', 'Chats']; // Error
// Var declaration resulting of the `selfDefending` transformation
// New variable is created with the previously declared function
var ChatsCtrl = function () {  
  /* protected code */
}
angular.module('starter.controllers').controller('ChatsCtrl', ChatsCtrl); 

To work around this issue, convert your function declarations into variable declarations with function expressions.

// Instead of `function ChatsCtrl($scope, Chats){...}`
var ChatsCtrl = function ($scope, Chats){  
  $scope.chats = Chats.all();
  $scope.remove = function(chat) {
    Chats.remove(chat);
  };
}
ChatsCtrl.$inject = ['$scope', 'Chats'];
angular.module('starter.controllers').controller('ChatsCtrl', ChatsCtrl);

Final Remarks

Now you know how to protect your AngularJS application by integrating Jscrambler into its build process, making your application safe and secure.

You’ve also learned how to work around issues that you may encounter when using Jscrambler with Angular.

Jscrambler’s Blog offers plenty of Angular content to help you build Angular applications.