0

I have an overlay modal for authentication but it presents various security issues, especially if a user has some knowledge of CSS, HTML and the Inspect Tools.

Is there a specific way that the HTML behind the modal be obscured or hidden so that the page source does not show the content?

The below snippet is only an example of a modal and not the actual code I am using. My code actually pulls in the wp-auth-login from Wordpress to the Frontend, but this gives basis to what Is happening. How do I ensure that the user cannot see the content behind a modal, especially not display:none on the overlay to see the content behind?

jQuery(document).ready(function($) {
  var $form_modal = $('.cd-user-modal'),
    $form_login = $form_modal.find('#cd-login'),
    $form_signup = $form_modal.find('#cd-signup'),
    $form_forgot_password = $form_modal.find('#cd-reset-password'),
    $form_modal_tab = $('.cd-switcher'),
    $tab_login = $form_modal_tab.children('li').eq(0).children('a'),
    $tab_signup = $form_modal_tab.children('li').eq(1).children('a'),
    $forgot_password_link = $form_login.find('.cd-form-bottom-message a'),
    $back_to_login_link = $form_forgot_password.find('.cd-form-bottom-message a'),
    $main_nav = $('.main-nav');

  //open modal
  $main_nav.on('click', function(event) {

    if ($(event.target).is($main_nav)) {
      // on mobile open the submenu
      $(this).children('ul').toggleClass('is-visible');
    } else {
      // on mobile close submenu
      $main_nav.children('ul').removeClass('is-visible');
      //show modal layer
      $form_modal.addClass('is-visible');
      //show the selected form
      ($(event.target).is('.cd-signup')) ? signup_selected(): login_selected();
    }

  });

  //close modal
  $('.cd-user-modal').on('click', function(event) {
    if ($(event.target).is($form_modal) || $(event.target).is('.cd-close-form')) {
      $form_modal.removeClass('is-visible');
    }
  });
  //close modal when clicking the esc keyboard button
  $(document).keyup(function(event) {
    if (event.which == '27') {
      $form_modal.removeClass('is-visible');
    }
  });

  //switch from a tab to another
  $form_modal_tab.on('click', function(event) {
    event.preventDefault();
    ($(event.target).is($tab_login)) ? login_selected(): signup_selected();
  });

  //hide or show password
  $('.hide-password').on('click', function() {
    var $this = $(this),
      $password_field = $this.prev('input');

    ('password' == $password_field.attr('type')) ? $password_field.attr('type', 'text'): $password_field.attr('type', 'password');
    ('Hide' == $this.text()) ? $this.text('Show'): $this.text('Hide');
    //focus and move cursor to the end of input field
    $password_field.putCursorAtEnd();
  });

  //show forgot-password form 
  $forgot_password_link.on('click', function(event) {
    event.preventDefault();
    forgot_password_selected();
  });

  //back to login from the forgot-password form
  $back_to_login_link.on('click', function(event) {
    event.preventDefault();
    login_selected();
  });

  function login_selected() {
    $form_login.addClass('is-selected');
    $form_signup.removeClass('is-selected');
    $form_forgot_password.removeClass('is-selected');
    $tab_login.addClass('selected');
    $tab_signup.removeClass('selected');
  }

  function signup_selected() {
    $form_login.removeClass('is-selected');
    $form_signup.addClass('is-selected');
    $form_forgot_password.removeClass('is-selected');
    $tab_login.removeClass('selected');
    $tab_signup.addClass('selected');
  }

  function forgot_password_selected() {
    $form_login.removeClass('is-selected');
    $form_signup.removeClass('is-selected');
    $form_forgot_password.addClass('is-selected');
  }

  //REMOVE THIS - it's just to show error messages 
  $form_login.find('input[type="submit"]').on('click', function(event) {
    event.preventDefault();
    $form_login.find('input[type="email"]').toggleClass('has-error').next('span').toggleClass('is-visible');
  });
  $form_signup.find('input[type="submit"]').on('click', function(event) {
    event.preventDefault();
    $form_signup.find('input[type="email"]').toggleClass('has-error').next('span').toggleClass('is-visible');
  });


  //IE9 placeholder fallback
  //credits http://www.hagenburger.net/BLOG/HTML5-Input-Placeholder-Fix-With-jQuery.html
  if (!Modernizr.input.placeholder) {
    $('[placeholder]').focus(function() {
      var input = $(this);
      if (input.val() == input.attr('placeholder')) {
        input.val('');
      }
    }).blur(function() {
      var input = $(this);
      if (input.val() == '' || input.val() == input.attr('placeholder')) {
        input.val(input.attr('placeholder'));
      }
    }).blur();
    $('[placeholder]').parents('form').submit(function() {
      $(this).find('[placeholder]').each(function() {
        var input = $(this);
        if (input.val() == input.attr('placeholder')) {
          input.val('');
        }
      })
    });
  }

});


//credits https://css-tricks.com/snippets/jquery/move-cursor-to-end-of-textarea-or-input/
jQuery.fn.putCursorAtEnd = function() {
  return this.each(function() {
    // If this function exists...
    if (this.setSelectionRange) {
      // ... then use it (Doesn't work in IE)
      // Double the length because Opera is inconsistent about whether a carriage return is one character or two. Sigh.
      var len = $(this).val().length * 2;
      this.setSelectionRange(len, len);
    } else {
      // ... otherwise replace the contents with itself
      // (Doesn't work in Google Chrome)
      $(this).val($(this).val());
    }
  });
};
html * {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

*,
*:after,
*:before {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

body {
  font-size: 100%;
  font-family: "PT Sans", sans-serif;
  color: #505260;
  background-color: #fff;
}

a {
  color: #2f889a;
  text-decoration: none;
}

img {
  max-width: 100%;
}

input,
textarea {
  font-family: "PT Sans", sans-serif;
  font-size: 16px;
  font-size: 1rem;
}

input::-ms-clear,
textarea::-ms-clear {
  display: none;
}

header[role=banner] {
  position: relative;
  height: 50px;
  background: #343642;
}

header[role=banner] #cd-logo {
  float: left;
  margin: 4px 0 0 5%;
  -webkit-transform-origin: 0 50%;
  -moz-transform-origin: 0 50%;
  -ms-transform-origin: 0 50%;
  -o-transform-origin: 0 50%;
  transform-origin: 0 50%;
  -webkit-transform: scale(0.8);
  -moz-transform: scale(0.8);
  -ms-transform: scale(0.8);
  -o-transform: scale(0.8);
  transform: scale(0.8);
}

header[role=banner] #cd-logo img {
  display: block;
}

header[role=banner]::after {
  content: '';
  display: table;
  clear: both;
}

@media only screen and (min-width: 768px) {
  header[role=banner] {
    height: 80px;
  }
  header[role=banner] #cd-logo {
    margin: 20px 0 0 5%;
    -webkit-transform: scale(1);
    -moz-transform: scale(1);
    -ms-transform: scale(1);
    -o-transform: scale(1);
    transform: scale(1);
  }
}

.main-nav {
  float: right;
  width: 80px;
  height: 100%;
  cursor: pointer;
}

.main-nav a {
  display: block;
  height: 50px;
  line-height: 50px;
  text-align: center;
  background: #292a34;
  border-top: 1px solid #3b3d4b;
  color: #FFF;
}

@media only screen and (min-width: 768px) {
  .main-nav {
    width: auto;
    height: auto;
    background: none;
    cursor: auto;
  }
  .main-nav a {
    display: inline-block;
    height: auto;
    line-height: normal;
    background: transparent;
  }
  .main-nav a.cd-signin {
    padding: .6em 1em;
    border: 1px solid rgba(255, 255, 255, 0.6);
    border-radius: 50em;
  }
}

.cd-user-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(52, 54, 66, 0.9);
  z-index: 3;
  overflow-y: auto;
  cursor: pointer;
  visibility: hidden;
  opacity: 0;
  -webkit-transition: opacity 0.3s 0, visibility 0 0.3s;
  -moz-transition: opacity 0.3s 0, visibility 0 0.3s;
  transition: opacity 0.3s 0, visibility 0 0.3s;
}

.cd-user-modal.is-visible {
  visibility: visible;
  opacity: 1;
  -webkit-transition: opacity 0.3s 0, visibility 0 0;
  -moz-transition: opacity 0.3s 0, visibility 0 0;
  transition: opacity 0.3s 0, visibility 0 0;
}

.cd-user-modal.is-visible .cd-user-modal-container {
  -webkit-transform: translateY(0);
  -moz-transform: translateY(0);
  -ms-transform: translateY(0);
  -o-transform: translateY(0);
  transform: translateY(0);
}

.cd-user-modal-container {
  position: relative;
  width: 90%;
  max-width: 600px;
  background: #FFF;
  margin: 3em auto 4em;
  cursor: auto;
  border-radius: 0.25em;
  -webkit-transform: translateY(-30px);
  -moz-transform: translateY(-30px);
  -ms-transform: translateY(-30px);
  -o-transform: translateY(-30px);
  transform: translateY(-30px);
  -webkit-transition-property: -webkit-transform;
  -moz-transition-property: -moz-transform;
  transition-property: transform;
  -webkit-transition-duration: 0.3s;
  -moz-transition-duration: 0.3s;
  transition-duration: 0.3s;
}

@media only screen and (min-width: 600px) {
  .cd-user-modal-container {
    margin: 4em auto;
  }
  .cd-user-modal-container .cd-switcher a {
    height: 70px;
    line-height: 70px;
  }
}

.cd-close-form {
  display: block;
  position: absolute;
  width: 40px;
  height: 40px;
  right: 0;
  top: -40px;
  background: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/148866/cd-icon-close.svg") no-repeat center center;
  text-indent: 100%;
  white-space: nowrap;
  overflow: hidden;
}

@media only screen and (min-width: 1170px) {
  .cd-close-form {
    display: none;
  }
}

#cd-login {
  display: none;
}

#cd-login.is-selected {
  display: block;
  padding: 4em;
  text-align: center;
}
<link href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/148866/reset.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js"></script>

<body>
  <header role="banner">
    <div id="cd-logo">
      <a href="#0"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/148866/cd-logo_1.svg" alt="Logo"></a>
    </div>
    <nav class="main-nav">
      <a class="cd-signin" href="#0">Sign in</a>
    </nav>
  </header>
  <div class="cd-user-modal">
    <div class="cd-user-modal-container">
      <div id="cd-login">
        LOGIN FORM
      </div>
      <a href="#0" class="cd-close-form">Close</a>
    </div>
  </div>
</body>
Darren
  • 2,176
  • 9
  • 42
  • 98
  • I don't know what exactly are you trying to achieve here, but you should not (must not) have your auth logic in the frontend. It's not going to be safe, no matter how *obscure* your HTML/JS is. – Jordi Nebot Feb 17 '18 at 07:09
  • Totally understand. I should have given more context. The Auth form is only present when a users login expires, at all other times the user will be redirected to a login page. However, in the event of the user login expiring it seems better fit to overlay the `wp-auth-login` as this will not affect any unsaved entries. I was hoping that there could be a way to do this via JS. I notice that a service called Auth0 (https://auth0.com/) does this but cannot figure out how. – Darren Feb 17 '18 at 07:15
  • Saying that I notice now that they have built the platform using React which would be quite easy to achieving. Problem is I am having to do this with Wordpress : ( – Darren Feb 17 '18 at 07:17

1 Answers1

0

At the end of the day, if you have code running on a client machine, security is out of the window. You can play games to make it more difficult for the user to discover, but let's first agree that perfect security given your implied constraints is not possible.

Within you're implied constraints, however, I can offer a couple of suggestions. When the authentication modal is visible/demanding attention, you might:

  • visibly blur the underlay; this just makes it look some degree of secure.

  • actually replace the underlayed DOM with some version of gibberish; the key point being that to be "safe from prying eyes", you need to actually remove anything that might be pried. You can either wholesale remove the sensitive bits, or you can scramble it. Your choice for your context.

    • If you want to keep the data around, for uber-fast redisplay after the user successfully authenticates, consider locking the necessary content inside of a private/inaccessible variable. Note that this still isn't secure to a determined user, but it raises the bar quite a bit. For total and complete security, you will have to actually and totally remove all sensitive information from the client machine and force them to recollect data once authenticated.
hunteke
  • 3,648
  • 1
  • 7
  • 17
  • That is spot on. Not sure if you read my comments above that give more context to how this is being implemented but you hit it on the head and I think that this is one topic where functionality is second - being that security is first, which should always be the case anyhow. Thanks – Darren Feb 17 '18 at 07:24