More Secure Sites Forms with Encrypted Keys

The power of Sites is the ability to publicly expose Salesforce data to the world.  In another post on the marketing blog, I showed one application of Sites for event registration forms which eliminates Lead duplicates & prevents prospects from having to retype their information.  While Sites is secure and battle tested, if you expose Salesforce data using the typical URL syntax of:

?id=00Q3000000PKsCa

a hacker could start guessing other IDs and either scrape information you show on the form for those IDs, or do any actions on those forms intended for your prospects and customers such as create bad event registrations or download white papers.
This article picks up where Ron’s article on creating encrypted text fields left off.

As Ron mentioned, a secure way to handle this problem is to use an encrypted key in addition to the object ID as URL variables, and here I’ll show a straightforward way to do that.  Using both a key & the ID helps protect against poorly randomized keys by increasing the length of the text that needs to match.

The steps to create encrypted access to public Sites forms:
1) Create a working Sites webform with all the proper security access settings
2) Create a custom field that serves as the encrypted key, and create a trigger to populate that field
3) Add a method to your controller to redirect the page if the ID and Key don’t match
4) Modify your Visualforce page to call this Apex function upon load
5) Modify your Email Template links to point to the page using both the encrypted key and the campaign member ID


1) Create working Sites form

This post will give you a great head start on getting a Sites webform up & running.


2) Create a custom field & trigger to populate the encrypted key

Next create a custom field called “EncryptedKey__c” and use a trigger to update it.  The custom field should be either missing from page layouts or read only to prevent changes.  This article has more background on how to make the trigger.  A trigger example for Campaign Member might look something like this:

trigger EncryptedKey on CampaignMember (before insert) {
for (CampaignMember c : Trigger.new) {
DateTime now = System.now();
String formattednow = now.formatGmt('yyyy-MM-dd')+'T'+ now.formatGmt('HH:mm:ss')+'.'+now.formatGMT('SSS')+'Z';
String canonical = c.id + c.Lead.FirstName + c.Lead.LastName + formattednow;
Blob bsig = Crypto.generateDigest('MD5', Blob.valueOf(canonical));
String token = EncodingUtil.base64Encode(bsig);

if(token.length() > 255) {
token = token.substring(0,254);
}//if
try {
c.EncryptedKey__c = Encodingutil.urlEncode(token, 'UTF-8').replaceAll('%','_');
}catch (Exception e){
system.debug('EncrytpedKey__c assignment failed:' +e);
}
}//for}

3) Add a method to your controller to redirect the page if the ID and Key don’t match

The Apex controller changes are actually pretty small. Here I’ve created a page reference method called “checkEncryptionKey”, which grabs the key from the URL using getParameters, and queries SFDC for the record with that key if found.

Public PageReference checkEncryptionKey(){
string key = ApexPages.currentPage().getParameters().get('key');
string cmid = ApexPages.currentPage().getParameters().get('id');
string errorpageref='http://www.insertyourerrorpagehere.com/';//page that it's redirected to if everything isn't as planned

if (key==null || cmid==null){
return new PageReference(errorpageref);//action that does the redirrecting
} else {
try{
Lead[] l= [SELECT ID FROM campaignmember WHERE encryptedkey__c = :key LIMIT 1];
if (l.size()>0){
if (cmid==l[0].id){
return null;//if both values match, leave the page as is.
} else {
return new PageReference(errorpageref);//action that does the redirrecting
} //if 3
} else {
return new PageReference(errorpageref);//action that does the redirrecting
} //if2
} catch (Exception e) {
ApexPages.addMessages(e );
return new PageReference(errorpageref);//action that does the redirrecting
}//try
}//else
}

4) Modify your Visualforce page to call this Apex function upon load

Modifying the VF page is almost trivial, adding the action to the page tag:

Before

<apex:page standardcontroller="CampaignMember" extensions="UpdateCampaignMemberClass" sidebar="false" showheader="false"> </apex:page>

After

<apex:page standardController="CampaignMember" extensions="UpdateCampaignMemberClass" sidebar="false" showHeader="false" action="{!checkEncryptionKey}">


5) Modify your Email Template links to point to the page using both the encrypted key and the campaign member ID

Finally, change your email template link to point to your landing page using the new encrypted key instead of the campaign member ID. See this post for more information on how to include links in email templates.

For the test example that points to the internal VF page, we’ll use URL in the email template to pass the key to your registration page:

https://na1.salesforce.com/apex/Web2CampaignMember?id={!CampaignMember.Id}&key={!CampaignMember.EncryptedKey__c}

A public sites page link would look something closer to this:

https://jdk470.force.com/?id={!CampaignMember.Id}&key={!CampaignMember.EncryptedKey__c}

That’s it! Now every new campaign member you create will have the encrypted key created by the trigger and your pages will be secure. This is not the only way, nor necessarily the best way to secure sites pages, but should work well against most threats.

Published
August 3, 2009

Leave your comments...

More Secure Sites Forms with Encrypted Keys