Semi-Protected Download URLs in WordPress via Transients, Cookies, and Pods
I spent some time in Client Oriented WordPress Development outlining in specific detail how I see each piece of WordPress coexisting together, and I also branched out and covered Pods and how it fits into my philosophy as well. It fills a very specific need I have and does it better than other plugins I’ve tried that attempt to solve the same issues.
While I won’t go into further specifics by copying and pasting that chapter of the book, but I’d love to outline a specific use case I came upon during a recent project where I took advantage of Pods unique placement in the WordPress ecosystem.
The problem at hand
On a recent project I needed to build a partial walled garden that held a bunch of content for registered member. I say partially walled because signing up was free and only took a few seconds, but it was walled none-the-less. As I was building one of the features, a Media Download section, I realized that it’d be pretty trivial for download links to these resources to be easily passed around. It wasn’t too likely, but I thought it’d be nice to somehow protect the download locations to prevent any sort of hotlinking.
This walled garden in this case wasn’t designed to be an epitome of security, it was to be used as a method of gaining further insight into the user base and encourage more helpful interaction given the content at hand. With that, I didn’t want to implement an elaborate system for what amounted to be a smaller task in the project as a whole. These user accounts were not powered by WordPress so it wasn’t as easy as firing
is_user_logged_in() to find out whether or not we’ve got a valid download request.
Given these circumstances, you might wonder why I’d bother protecting the downloads at all. While not a completely essential feature, I thought it’d be a nice thing to do for the client should they for one reason or another end up in a situation where a download link got tweeted out by a popular account and in doing so exposed an asset they would have preferred to remain behind the wall. It’s not an elaborate feature, but it’s nice to have.
The first part of the implementation is allowing the client to manage the downloads on their own. These downloads were to be offered on one of the pages within the walled garden, in this particular case a WordPress Page. Below the content there’s to be a list of the downloads, each with a title, a short blurb, and a link to the actual file itself. We’re going to use a great plugin called Custom Field Suite to implement this functionality.
Custom Field Suite is a great plugin I find myself using all the time that does pretty much what it says on the tin: let you easily create a nice UI for a front end to Custom Field data. Custom Fields are simple key/value pairs that get saved with all WordPress posts and will make a great storage mechanism for this data.
Our Field Group is going to be pretty simple. The whole thing will be powered by a Loop Field that’ll let us add and reorder any number of files to the page. Each entry within the Loop Field will have a subset of three fields, each representing the piece of data we need.
With the Field Group implemented and our Placement rules defined, the edit screen for our Member Downloads page now has a really nice way for us to populate some files to be downloaded.
If we weren’t trying to protect the download URL for these files, we could simply use the functionality built into Custom Field Suite to pull in the file URL and include that as a link target. We’re going to take things a step further and introduce Pods to help implement a non-invasive way to protect the download link from being passed around.
Integrating Pods and protecting the URLs
There are a few ways to go about implementing protected download URLs, but in this circumstance I’d like to take advantage of WordPress’ Nonce system. I am a huge fan of Nonces. If you aren’t familiar, please have a read of Mark Jaquith’s overview from back in 2006 for WordPress 2.0.3. Essentially, a nonce is a number used once and can be used to validate requests and for other security-related actions you might want to take. Unfortunately WordPress Nonces are contingent on the user being logged in else they’re based on the same user ID (0) rendering them useless. Never fear, there’s an alternative we can use.
The essential idea behind a nonce is to generate it based on the user, the action, and the time of the action. Since we don’t have WordPress user IDs to work with, we can make our own nonce system using WordPress’ Transient API, another favorite of mine. Transients are time-sensitive database records that expire after a certain duration. That solves our time requirement. We can come up with a unique way to generate a hash based on the attachment ID we need to eventually retrieve. That solves our action requirement. The last bit is the most difficult: tying the request to the user. This can be accomplished by the combination of using our Transient record remotely and a cookie locally.
Generating the link and handling the request
To protect our download locations, we’ll build a URL using the attachment ID stored by Custom Field Suite combined with our custom nonce that’s tied to the user who must be logged in (to the 3rd party account system) to view the page that would generate the link in the first place. That way the URL will be tied to the user viewing the page (via the cookie) and passing it along to a friend will result in an invalid request we can handle any way we wish. We can further protect the request by integrating the user’s IP into the Transient record. The process overall will look something like this:
- Member Downloads page is requested
- A cookie is created (if it doesn’t exist yet, or has expired) that stores a unique hash for the user based on their IP
- A nonce is generated for each File ID found in the Custom Field Suite Field Group (making use of the generated user hash)
- A Transient is stored that holds the file ID, user IP and the generated nonce
- When a link is clicked, the request is sent to our endpoint and passes the nonce
- On the server, the nonce is used to determine if we have a valid Transient
- If the IP checks out, we grab the real file location and send it back
The integration of a cookie increases security based on the possibility of the IP alone not being unique enough. It’s likely overboard given that we’re only semi-protecting the download links, but adds another level of security even though the cookie data can be spoofed.
The last piece of the puzzle will be setting up the actual target destination for these newly formed download requests; our endpoint. We could power it using a WordPress page, but I tend to stray away from using WordPress Pages as endpoints for implementations like this because they are potentially confusing to a client. A client might see the endpoint in their listing of Pages and send it to the Trash because they’re not sure why it was there in the first place.
We can avoid that potential issue by using Pods to set up and handle the endpoint behind the scenes. Pods Pages provide a way for you to set up custom endpoints that correlate either to raw PHP for a WordPress template file. We can set up a Pods page for
download/* that will be triggered by any request to
http://example.com/download/* with the final URL parameter being our nonce. The handler (either raw PHP or a template file) will validate the nonce against the Transient we set and compare the request IP with the one stored. We can take it a step further by validating the cookie as well. Once our validation has taken place, we can send the download back as requested.
We can add additional wildcard parameters to the Pods Page and in doing so further refine the request if we wanted. Items can be segmented into better human-readable URLs for example, but given the context that may be a bit overboard. That’s one of the great things about Pods though, the option is there if you want to take advantage. If we wanted to structure the Transient in a different way and pass along the file ID as a URL parameter instead of storing it in the Transient and simply compare only the nonce we could totally do that too. The details are yours.
Sample code? What sample code?
This implementation is in part an experiment for these types of articles on Monday By Noon. It correlates with something I thought a lot about while writing Client Oriented WordPress Development and tried to draw a line between the usefulness of copy-and-paste-able code and the actual discussion of a concept. This is one of those situations where I feel the discussion is more valuable than code snippets, but I’d love to hear what you guys think about that. Would this be of better use with a fully working, commented implementation to read through? Do you prefer to implement the details in your own way?