Tuesday, November 20, 2012

POSTing sensitive data over VisualForce

The Problem

My current project has a requirement for passing credentials from SFDC to an external site for auto-logging in users.  The requirement is a bit different from SSO, as these are Sales support representatives needing to log in to their customer's orgs for troubleshooting purposes.  The credentials are inherently sensitive, since they are username and passwords.

The current legacy system simply posts the credentials to a Cold Fusion form over HTTPS.  To mimmick this functionality, making it slightly more secure I implemented the following:
  The client's integration into SFDC stores the username / password data in encrypted fields, only allowing the System Admin to view the password data.  Secondly, the auto login function does its best to obscure the private information.  Furthermore, the auto login must be an HTTP POST to a form, rather than an HTTP GET.

Posting from VisualForce

POSTing from Visualforce is a bit tricky.  There's no real good way of going about it.  I've gotten comments on twitter (@dancinllama, by the way) regarding using the action parameter to do a HTTP callout on load.  However, the VF action also has to redirect to the page keeping the user logged in.  When I attempted this route, the browser never maintained the session.  Perhaps I was doing something wrong here, but I decided to go with a different solution.

In order to POST from Visualforce, I created a simple form with a username and password field.  The form was a straight HTML form, not utilizing the standard VF component.  On page load, JavaScript submits the form (to an HTTPS URL) and auto-logins the user.

Security through Obfuscation

The form itself was simple, but lead to a potential security flaw:  What if a "hacker" viewed the HTML source?  Even though the form input utilizes type="password" for masking in the UI, any schmuck can view the source and see the password in plain text.  Ruh-roh.  The solution was to utilize @RemoteAction call to populate the password value before the form was submitted.

The Code

Below is the simple @RemoteAction method.  Since the method is static, I had to pass in three variables to perform a dynamic query for retrieving the password on the fly.


  @RemoteAction
  public static String getPass(String field, String apiName, String recordId){
    String ans = null;
    try{
      sObject sobj = Database.query('Select ' + field + ' From ' + apiName + ' Where Id=:recordId');
      ans = (String)sobj.get(field);
    }catch(Exception e){}
    return ans;
  }

The remote action is called on page load from the VisualForce page using javascript wizardry, which passes in the object api name, the field api name, and the id of the record to query.



<form method="post" id="uform" name="uform" action="{!url}">  <input type="text" name="username" value="{!username}" />  <input type="password" name="password" value="" />  <input type="submit" value="Submit" /></form>



<script type="text/javascript"> function getPass() {
   Visualforce.remoting.Manager.invokeAction(
     '{!$RemoteAction.MyClass.getPass}',        '{!passwordFieldName}'        '{!objectApiName}'        '{!recordId}',        function(result, event){
          if(event.status){
            document.getElementById('pass').value=result;
            document.uform.submit();
          }
        },
        {escape: true}
    );
}
if(window.attachEvent){
    window.attachEvent("onload",getPass);
}else{    window.addEventListener("load",getPass, false);
}
</script>


Although it's a bit clumodgeony, it gets the job done.  The end result is the user is redirected to "url" and if they have valid credentials, are logged into the external system.  If you have any questions, comments, or critiques, please leave them below or ping me on twitter (@dancinllama).