Preventing users from using DevTools to bypass upgrade modals

Preventing users from using DevTools to bypass upgrade modals

·

3 min read

Let's say you've built a React app and it's profitable. You've also guarded some of your features behind an upgrade paywall like this:

upgrade-paywall.png

The Problem:

However, some of your smarter customers have discovered a way to bypass the modal so as to access the second tier features without paying for it. Using React DevTools, they simply inspect your component responsible for rendering the upgrade modal and poke around until eventually, they find a prop that looks like it has the potential to make your modal disappear.

before-inaccessible.png

So they click it and... Voila! They have full access to your upper tier features without paying for it.

after-accessible.png

How can you prevent this from happening? Ultimately, due to the browser's client-side nature, there isn't a bulletproof way. However, there are a couple of ideas that I've brainstormed to make it a little more difficult.

Idea #1 : Replace boolean with functional props

The downside in using Boolean props is that it exposes checkboxes in React DevTools. Bypassing the modal then becomes a game of 'what happens if I click this prop?'. I asked myself if there is a way to hide those checkboxes. I then experimented with removing the checkbox option altogether by using functional props. So instead of depending on an initial Boolean prop like this...

class Features extends React.Component {
  constructor(props) {
    super(props);

    // disabled state is hard coded to be true here 
    // but can come from another prop or via React.createContext etc.
    this.state = { disabled: true };
  }

  // then eventually you render the Upgrade Modal
  render() {
     // ... jsx code here
     <UpgradeModal showUpgrade={this.state.disabled} />
     // ... more jsx code here
  }

... you can transform that to use functional props so as to remove the ability for the user to checkbox that particular prop:

  render() {
     // ... jsx code here
     <UpgradeModal showUpgrade={() => this.state.disabled} />
     // ... more jsx code here
  }

Try it on Codepen

Of course, this is only makes a little bit more harder for the user to bypass. Perhaps this might pave the way to wrap/pass more of your sensitive data. But what if the determined user climbs your component tree to find the checkbox that the disabled depends on? This was the nature of the problem for a real world task I was asked to find a solution for. I needed a way to remember the initial state of the component that is unaffected by the DevTools. Then it occurred to me, there is one JS concept that does exactly that.

Idea #2: Use a closure

What if I called a closure function within the constructor that would remember the initial state of disabled. Once the state was captured, I could call that closure's return value (which itself is a function) to then use as a conditional test to disable any functionality of features.

class Features extends React.Component {
  constructor(props) {
    super(props);
    this.state = { disabled: true };

    // use closure to 'memorize' initial value of disabled
    this.wasDisabled = this.wasInitStateDisabled(this.state.disabled);
  }

  // constructor will populate wasDisabled with return value
  // of wasInitStateDisabled() which is a closure function
  wasDisabled = () => {};

  // closure that returns a function.  When that returned function
  // is called, it returns the previously 'memorized' value
  wasInitStateDisabled(initVal) {
    return function () {
      return initVal;
    };
  }

  handleClick = () => {
    // if the initial state was disabled, then even if user
    // bypasses upgrade modal, clicking a feature will do nothing
    if (!this.wasDisabled()) {
      alert("Now accessing 2nd Tier features");
    }
  };

  render() {
    // jsx code here ...
  }

Try it on Codepen

Conclusion

As it turned out, I applied a different prop to test our state, but the decision was made that the back-end team would detect and handle any such overrides. My ideas remain here on this blog for you to try out yourself to see if it helps.