When ColdFusion manages clients (or sessions), it creates a token that has a "secret" relationship with the ID and stores both the CFTOKEN and CFID on the client's browser or adds them to the URL. Because it's hard to guess what token should be supplied for a given ID, this enhances security.
In a clustered configuration, you can't enforce continuity between pages by confirming these elements against session variables in the server's memory; because, the user may not always be served by the same machine she logged in on. You could use client variables from a common database, but it would be nice not to have to hit the database for every page.
This example creates a token containing user identification and permissions and pairs it with a UserID to make it hard for tampering users to create a valid combination. To do this kind of thing for yourself, you need to create strings using rules that aren't easily guessed by users and for which likely user manipulations won't result in valid-looking data. The result gives you the ability to confirm both identification and permissions on every page, without a database hit, even if you're in a clustered configuration.
Here's a tool you can use to build and read data in scrambled form to do this kind of work. Along the way, you'll generate a range of random numbers, insert characters into a string, work with number bases, read and remove characters from a string, do modulus division, and work with individual bits of a number.
Use pieces of this idea to protect other things that are precious to you, such as checksums in a shopping cart.
Multiply the random number by the secret number and add the UserID to the result. The secret number must be larger than the largest UserID you plan to use; because, you'll remove multiples of the secret number when you decode this value to get back to the UserID.
<cfset UserID=18> <cfoutput>UserID=#UserID#</cfoutput>. This would typically come from a query. <p> <cfset Temp=randRange(54,210)*12345+UserID> Temp=randRange(54,210)*12345+UserID: <cfoutput>#Temp#</cfoutput> <p>
<cfset Group=5> <cfoutput>Group=#Group#</cfoutput>. This would typically come from a query. <p> <cfset Temp=insert(Group,Temp,3)> Temp=insert(Group,Temp,3): <cfoutput>#Temp#</cfoutput> <p>
The product of the secret number and the low end of the random range has to have at least the number of positions you want to come before your inserted string for this to work (unless you want to check the length and tack it on the end).
We normally use base 10 (digits 0 through 9) in daily life. We sometimes use base 16 (digits 0 through 9, A, B, C, D, E, and F). This example converts the input to base 20, something less common. You can use larger bases than this, but be sensitive to what might get spelled by your string. If you used base 36 (all digits plus the entire alphabet), you might upset someone from time to time. This example forces the result to upper case because it looks better that way.
<cfset Token=ucase(FormatBaseN(Temp,20))> Token=ucase(FormatBaseN(Temp,20)): <cfoutput>#Token#</cfoutput> <p>
<cfoutput>ENCODE: UserID=#UserID#; Group=#Group#; Token=#Token#</cfoutput><p>
<hr> <cfset TempIn=inputBaseN(Token,20)> TempIn=inputBaseN(Token,20): <cfoutput>#TempIn#</cfoutput> <p>
<cfset GroupIn=mid(TempIn,4,1)> GroupIn=mid(TempIn,4,1): <cfoutput>#GroupIn#</cfoutput> <p>
Now we need to remove the group to use arithmetic on the result. The removeChars function works just like the mid function. It will remove every character starting at a given position and continuing for the length specified.
<cfset TempIn=RemoveChars(TempIn,4,1)> TempIn=RemoveChars(TempIn,4,1): <cfoutput>#TempIn#</cfoutput> <p>
In this example, the mod function removes the secret number, leaving the remainder: the UserID.
<cfset UserIDIn=TempIn mod 12345> UserIDIn=TempIn mod 12345: <cfoutput>#UserIDIn#</cfoutput> <p>
If the UserID in the token matches the UserID alongside the token, then the group is probably valid also.
<cfoutput>DECODE: UserID=#UserIDIn#; Group=#GroupIn#</cfoutput><p>
Because you can use the bitOr function to see if a specific bit is present in a number, you can easily test these bit-wise permissions. The bitOr function merges (shows the union) of the bits of the two inputs it's given. For example, the bit pattern of 5 is 101; the pattern of 2 is 010. Their union is 111 (7). If the bit in the second number is already in the first, their union will be the same as the first number. For example, the bit pattern of 3 is 011; the pattern of 1 is 001; their union is 011. Because the 1 bit is already part of the number 3, merging it with 3 leaves 3 unchanged.
In the example below, because the group is five, bits four (admin) and one (statistics) are present. To see this decoding work, simply save all this code as hiding.cfm, change the group, and watch the result.
<cfoutput>Permissions:<br> <cfif bitOr(Group,4) is Group>Admin (4)<br></cfif> <cfif bitOr(Group,2) is Group>Work (2)<br></cfif> <cfif bitOr(Group,1) is Group>Statistics (1)</cfif> </cfoutput>
Your data doesn't always remain under your control. With checksums generated through techniques like this, you can add a level of security to help you sleep better at night.
This kind of encoding lets you have more trust in the data you expose to the client and get work done with fewer hits to the database. Strings which look like simple (although unintelligible) values can be complex storehouses for data. Using these techniques, you can hide data in plain sight. =Marty=
[Lines have been added since initial publication to make the code example more self-explanatory.]