Tim Riley

Generating semi-private, obfuscated resource sharing URLs in Rails

March 11th 2008

Recently I was working on an app that required semi-private, obfuscated URLs for sharing pages and feeds with non-registered members, much like Backpack does for its page feeds. Specifically, I wanted a URL with a long code in it of some kind that would make it difficult to guess.

I could not find anything on the net addressing this kind of requirement, much less in a RESTful way, so I rolled up my sleeves and built one independently. Here’s how I did it.

In this example, I want to share Activity resources, which I am exposing in the typical way in Rails' routes.rb:

map.resources :users do |user|
  user.resources :activities, :path_prefix => '/:user_id'
end

This routes provides access to the 7 standard CRUD actions in the ActivitiesController: index, show, new, create, edit, update, destroy. The only one of these that relates to listing multiple activities is index, but that action is already used to display activities to logged in users. So, a new action is needed, which we shall called “shared”. I will create 2 new named routes to provide access to this:

map.shared_activities '/:user_id/activities/shared/:key',  
  :controller => 'activities', :action => 'shared', :conditions => { :method => :get }

map.formatted_shared_activities '/:user_id/activities/shared/:key.:format',  
  :controller => 'activities', :action => 'shared', :conditions => { :method => :get }

Take note of the :key component of these paths. This is the private code required to ‘unlock’ the shared activity pages. The key belongs to Sharing objects:

class Sharing < ActiveRecord::Base
  belongs_to :user
  validates_uniqueness_of :key
  after_create :create_key

  private

  def create_key
    self.key = Digest::SHA1.hexdigest(Time.now.to_s.split(//).sort_by {rand}.join)
    self.save
  end
end

So, if a user wants to share his activities, he can create a sharing object, which generates the key for the URL that they can share with their non-registered friends. One of these URLs will look like this:

http://myapp.com/tim/activities/shared/a258f423366a2a07ffd3afec8c07f1bed8e07ba9

This will load the shared action in the activities controller, which is also protected by a before_filter that will only allow access if the key in the URL is valid for the user.

class ActivitiesController < ApplicationController
  before_filter :login_required, :except => :shared
  before_filter :get_user
  before_filter :sharing_required, :only => :shared

  def shared
    @activities = @user.activities.paginate(:order => 'created_at DESC', :page => params[:page])
  end

  private

  def get_user
    @user = User.find_by_login(params[:user_id])
  end

  def sharing_required
    # pull the key out of the URL and verify that the user has one to match
    unless Sharing.find(:first, :conditions => [ 'user_id = ? AND key = ?', @user.id, params[:key] ])
      flash[:error] = 'You do not have permission to view this page'
      redirect_to '/'
    end
  end
end

To flesh out the implementation, all you need to do is the following:

  • Add views for the shared action in the activities controller
  • Write a builder view to output the sharing action in RSS (explained here)
  • Include a sharing resource (nested under the user resource) in routes.rb and add a CRUD interface for creating and manipulating Sharing objects
Tim Riley

Hi, I'm Tim Riley. I'm a web developer based in Canberra, Australia, where I work at a small design studio called Icelab. This is my blog, where I can share with you the things I learn as I go about building products for the web.

What I Build

Recent Presentations

  1. Facebook Authentication with OAuth
  2. JavaScript Testing with Capybara and Cucumber
  3. Using Titanium to Build Mobile Apps with JavaScript (audio)
  4. An Introduction to Sinatra
  5. Building your Capistrano Recipe Book