DEV Community

Cover image for Use JavaScript Proxy to protect internal properties
Phuoc Nguyen
Phuoc Nguyen

Posted on • Originally published at phuoc.ng

Use JavaScript Proxy to protect internal properties

Protecting the internal properties of an object can be crucial in many situations. For example, if you're working on a web application that handles sensitive user data, it's vital to safeguard that data from unauthorized access. By using private properties, you can prevent malicious actors from accessing or modifying sensitive information.

Encapsulation is another important practice in software development. It involves bundling data and behavior into a single unit (i.e., an object) and controlling access to that unit. Private properties allow you to encapsulate data within an object and expose only the necessary methods for manipulating that data.

When writing tests for your code, it's helpful to isolate certain parts of your codebase to ensure they behave as expected. Using private properties allows you to test individual components without worrying about unintended side effects from other parts of your code.

Let's say we have an object that represents all the information for a user account. In reality, the object contains data queried from a database and may include sensitive information like a social security number (SSN).

let user = {
    name: "John Smith",
    age: 42,
    ssn: "...",
};
Enter fullscreen mode Exit fullscreen mode

How can we prevent someone from accessing or changing certain properties from outside? In this post, we'll learn how to use Proxy to achieve that goal.

Preventing accessing and modifying properties

With the approach we're about to implement, we can protect specific properties by name. However, we've decided to protect all properties that follow a certain naming convention.

In programming, we typically use an underscore (_) to indicate internal properties. For example, the SSN of the user object mentioned earlier should be named _ssn.

To achieve this, we define a get trap on the Proxy object that checks if the requested property begins with an underscore. If it does, we throw an error. If not, we return the property value.

Here's how we'll make it happen:

const protectInternalProps = (obj) => {
    const handler = {
        get(target, key) {
            if (key.startsWith('_')) {
                throw new Error(`Cannot access internal property '${key}'`);
            }
            return target[key];
        },
    };

    return new Proxy(obj, handler);
};
Enter fullscreen mode Exit fullscreen mode

Similarly, when we attempt to modify an internal property using the assignment operator, it triggers the set trap. In our case, we have defined a set handler on the Proxy object that checks if the property name starts with an underscore (_). If it does, we throw an error. Otherwise, we set the value as usual.

Here's how it all comes together:

const protectInternalProps = (obj) => {
    const handler = {
        set(target, key, value) {
            if (key.startsWith('_')) {
                throw new Error(`Cannot modify internal property '${key}'`);
            }

            target[key] = value;
            return true;
        },
    };

    return new Proxy(obj, handler);
};
Enter fullscreen mode Exit fullscreen mode

In the code above, you'll notice that if we attempt to modify any properties that start with an underscore (_), an error message reading Cannot modify internal property will be thrown. However, if we try to modify any other property, the value will be set as usual.

Now, let's experiment with it and see what happens.

const user = {
    name: "John Smith",
    _ssn: "XXX-YY-ZZZ",
};

const protectedUser = protectInternalProps(user);

console.log(protectedUser.name);    // John Smith

// Throw error `Cannot access internal property '_ssn'`
console.log(protectedUser._ssn);
protectedUser._ssn = '123-45-678';
Enter fullscreen mode Exit fullscreen mode

But is the current level of protection enough to safeguard these properties? Unfortunately, it is still possible to delete the property using the delete function.

delete protectedUser._ssn;
Enter fullscreen mode Exit fullscreen mode

Another issue is that we can check the existence of the properties of the object by looping over them.

Object.keys(protectedUser);     // ['name', '_ssn']
Enter fullscreen mode Exit fullscreen mode

Fortunately, Proxy provides additional traps to solve these issues. Now, let's move on to the next sections.

Stopping properties from being deleted

The deleteProperty trap lets us intercept property deletion. If we want to prevent deletion of private properties, we can throw an error if the property name starts with an underscore (_).

Here's how we can modify our handler object to include the deleteProperty trap:

const handler = {
    deleteProperty(target, key) {
        if (key.startsWith('_')) {
            throw new Error(`Cannot delete internal property '${key}'`);
        }

        delete target[key];
        return true;
    },
};
Enter fullscreen mode Exit fullscreen mode

If we attempt to use the delete function to remove an internal property, an error will be thrown with the message Cannot delete internal property.

const user = {
    name: "John Smith",
    _ssn: "XXX-YY-ZZZ",
};

const protectedUser = protectInternalProps(user);

// Throw error `Cannot delete internal property '_ssn'`
delete protectedUser._ssn;
Enter fullscreen mode Exit fullscreen mode

Stopping object properties from being iterated

The ownKeys trap is a handy feature that helps solve the problem of accessing internal properties. Whenever the Object.keys() method is called on an object, this trap is triggered, and it returns an array of keys that can be accessed.

In our case, we only want to return keys that don't start with an underscore (_). We can do this by using the filter method to remove all keys that start with an underscore.

Here's how we can modify our handler object to include the ownKeys trap:

const handler = {
    ownKeys(target) {
        return Object.keys(target).filter(key => !key.startsWith('_'));
    },
};
Enter fullscreen mode Exit fullscreen mode

By implementing this, when you call Object.keys() on our Proxy object, it will only return an array of non-internal keys.

const user = {
    name: "John Smith",
    _ssn: "XXX-YY-ZZZ",
};

const protectedUser = protectInternalProps(user);

console.log(Object.keys(protectedUser)); // ['name']
Enter fullscreen mode Exit fullscreen mode

This also works with the for ... in loop.

for (let key in protectedUser) {
    console.log(key);
}
// ['name']
Enter fullscreen mode Exit fullscreen mode

By implementing these traps on our Proxy object, we can now make sure that any properties that start with an underscore (_) are genuinely private. This means that they cannot be accessed or modified from outside the object.

Conclusion

To sum up, using a Proxy object to protect internal properties is a smart way to keep sensitive data safe in your web applications. By implementing the get, set, deleteProperty, and ownKeys traps on the Proxy object, you can prevent unwanted access or modification of private properties.

This approach also helps you maintain encapsulation and test individual components of your codebase without worrying about unintended consequences. Overall, it's a powerful technique that every developer should consider when working with sensitive data in their applications.


If you found this series helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks ๐Ÿ˜. Your support would mean a lot to me!

If you want more helpful content like this, feel free to follow me:

Top comments (0)