Creating Expiring Web PagesBy Scott Mitchell
In last week's article, Passing Tamper-Proof QueryString Parameters, I illustrated one technique for passing tamper-proof querystring parameters from one Web page to another. (In reality, I've not used the techniques discussed in last week's article to create tamper-proof URLs within a website; rather, I've used it as a means to pass authentication information from one website to a partner website.) In my discussion in last week's article I did omit one very important safe-guard for such systems: how to prevent replay attacks (thanks to alert 4Guys reader Phil D. for pointing out this ommission).
The problem with the tamper-proof querystring parameter values approach I shared last week was that the tamper-proof check
does not do any sort of expiration check. That means once a URL has been created it is good forever. To see where this can
cause problems, imagine that we are using this technique to pass authentication information from one website (Site A) to a partner
website (Site B). Specifically, Site A sends to Site B a querystring that looks like:
Both websites will have to agree upon the secret salt and share that with one another, but as long as the end user is not
privy to this knowledge, they cannot craft their own, valid authenticating querystring parameters. If end user Sally clicks from
Site A onto Site B, she'll see the
UserName parameter in the querystring. However, if she tries to alter it,
changing it from "Sally" to, say, "Maria", the receiving page's digest check will fail.
The problem of replay attacks remains, however. If Sally bookmarks the link from Site A to Site B, and her evil coworker Theo finds this bookmark, Theo can then visit Site B authenticated as Sally. Theo can do this days, weeeks, or months after Sally last visits Site B. What we'd like to do, is make that link from Site A to Site B only "active" for a short period of time, say 60 seconds. That way, even if Sally bookmarks the link directly from Site A to Site B, if she - or anyone else - visits that link more than 60 seconds after the link was created, they'll get an error on Site B, saying that the link has expired.
In this article we'll see how to prevent against these types of replay attacks. The method examined can be used in the authentication scenario just described, or can be used to creating "self-expiring" links. Read on to learn more!
Creating a Link That Expires
Last week's article illustrated how to pass tamper-proof querystring parameters (if you have yet to read that article, please do so before continuing on). To create a link that expires, all we need to do is pass the time the link was created in the querystring as a tamper-proof URL. The receiving page, then, can examine the time and determine whether or not the link has expired. I prefer to let the receiving page determine when a link expires. However, if you trust the sender you could adjust the algorithm to allow the sender to specify for how long the link is valid. In that case, rather than having the sender create a tamper-proof querystring value with the time the link was created, you'd add a tamper-proof querystring value indicating when the link expires.
Let's look at a very simple example. Imagine that I have a site with two pages,
I want to only allow people to visit
PageB.aspx if they have first read through
PageA.aspx and checked
an "I Have Read this Page" checkbox. (Clearly there are many ways to accomplish this, and using an "expiring page" might be
a bit of overkill, but it should illustrate the point nicely.) Let's first look at
This page presents the legalese (the "Blah blah blah" part) followed by a CheckBox and a Button Web control. When the
Button is clicked, a postback ensues and the
GotoPageB event handler is executed. This event handler first
checks to ensure that the CheckBox has indeed been checked. If it has not, the user is kept on this page and
shown an error message. If the user has checked the CheckBox they are redirected via a
ExpiringDemoB.aspx, passing in the querystring the current date/time formatted as the four digit year,
followed by the two-digit month, followed by the two-digit day of the month, followed by the two-digit hour as of a 24-hour clock,
followed by the two-digit minute, followed by the two-digit second. The
CreateTamperProofURL() method, which
is omitted for brevity from the above code section, was discussed in detail in Passing
Tamper-Proof QueryString Parameters. As you'll recall from that article,
CreateTamperProofURL() uses MD5
to create a digest of the tamper-proof querystring parameter(s) (
Time, in this example).
That's all there is for
PageA.aspx. All that's left is to create
PageB.aspx, where we'll
parse the querystring and determine if request is too dated or not.
Creating the Receiving Page and Determining if the Request is Stale
In order to determine if the incoming request to
PageB.aspxneeds to perform the following checks:
- Ensure that a
Timequerystring parameter is included,
- Ensure that the Digest matches up with the
- Determine the delta between the
Timevalue in the querystring and the current date/time, and see if that delta is greater than the expiry of the page.
As with the previous code snippet, some of the code here has been omitted for brevity; refer to the live demo for the complete source code and consult Passing Tamper-Proof QueryString Parameters for an explanation as to how the receiving page ensures that the digest matches up.
PageB.aspx does three things: first it makes sure that the
parameter is provided; next, it makes a call to
EnsureURLNotTampered() to verify that the user hasn't attempted to
tinker with the
Digest values; lastly, it reads in the
Time value passed in
the querystring and determines how many seconds have elapsed between the passed-in time and the web server's current time.
If that delta is greater than 15 seconds, it displays a warning message, informing the user that the link has expired.
The example just presented was designed to highlight the concept, not to make any claims as to the best practices for implementing expiring URLs. For example, in the code snippets we just examined, all of the logic is placed squarely in each page. This approach would be much less tedious to apply if the timestamp checking and expirary rules were moved to a base class or, even better, an HTTP Module.
Furthermore, the expiry for this example was set very low - 15 seconds - which is likely too short in real applications.
Users might be coming over dial-up modems, or there may be a hiccup between the client and your web server. In any case,
if it takes longer than 15 seconds between when the
Response.Redirect() crafts the
and when the client's request for that page reaches the web server, then the user will arrive at the linked to page seeing
the expirary message. Talk about a less than ideal user experience!
In this example the expiring page was arrived at from another web page in the same site. If you are using this technique
to pass information from one website to another, realize that these web servers may be in different time zones. The code
provided as-is does not take into affect any time zone differences when calculating the delta between the time the
Time parameter and the current date/time. To overcome this, both web servers should agree to talk about
times in terms of UTC time. The DateTime structure has methods to convert local time to and from UTC time (see
Additionally, different web servers may have a skew between their system clocks. (That is, the receiving web server might
think the UTC time is 8:23:08, but the sending time server's clock might be slow and think the UTC time is 08:21:52.)
Hopefully both are using some Internet-based time service to periodically update their clocks, but even so, between refreshes
there may be some drift. Hence, you must take care not to make the window that a link is valid too small.
Lastly, if you are creating such expiray links and placing them on web pages where they may be spidered by search engines, the search engines may store the resulting page's URL using the time-expiring URL. This means that a potential visitor who clicks on a search engine result may see the "This link has expired" message, which would be annoying for the user.
FYI, I have used this technique in two real-world applications I've worked on, both of which involved passing authentication information from one website to a partner website. With the first project, the time window was rather short at about five minutes. The system was used as follows: a person would visit Site A and on their Site A homepage there'd be a link to complete a certain task that was handled by partner Site B. Clicking that link would build up a URL that included their authentication information and the time the URL was created (in UTC time), sending it to the receiving web server. The receiving web server, then, would verify the integrity of the digest and then make sure that the delta between the current time and sent time was no greater than 10 minutes. The second project used this expiring URL to offer people a "window" during which they could sign up for the site. A person would receive an email with instructions to sign up via a link provided in the email; the link in the email would "expire" in 30 days, meaning that if the user attempted to go signup 30 or more days after receiving the email, they'd be shown a message informing them that the opportunity they had to sign up had passed.
In this article we examined how to create expiring web pages. This feat is accomplished by creating tamper-proof querystring parameters (a technique examined in a previous article) and sending along the time in the querystring. The receiving page, then, is able to determine whether or not the link used to arrive at the page has expired or not based on some business logic.