/ Load Balancing

How to Route Using Cookies

Cookies can be a useful way to store information. They can also be a clever way of routing your users; if the user has a certain cookie, send them there instead of here. We'll show you how to route user sessions based on information contained within an encrypted cookie.

We'll explore a lightweight example in Rails:

If a user visits our application at wizardry.ca without a userId stored in their session then we will bring them to our standard marketing page on our root domain. If they do have a userId stored in their session then they will be routed to their application dashboard while remaining on the root domain.

We want to expose an encrypted Rails application key, userId, onto an HTTP header that we can use for nifty routing.

Typically, this would be tricky to implement. You can configure Nginx using Lua scripting to decrypt an encrypted cookie, then script further or use nginx's map to assign headers and accomplish header-based routing. But that's a lot of work and we've got a simple and nifty method to share with you.

First, we'll get into the basics of Rails session cookie storage. After that, we'll put our example in motion to demonstrate how we can provide a smoother experience for our users. Let's dive in!

Possibly Delicious Cookies

Since Rails 4, Rails made an adjustment to how cookies are stored.

The Cookie Monster

Coooookies?!

No! Not those cookies. These cookies:

Before Rails 4, Rails had an object known as the CookieStore. The CookieStore was placed within the client's browser and it held encoded - not encrypted - information about the user. Typically, a cookie would contain a userId and any other boolean, integer, or string information that represented a particular user - as long as it was under 4kb. This was handy! It enhanced the user's experience by remembering things about them that the application could access on repeated visits.

The fatal flaw came within the encoding. It was only done in Base64. A malicious actor could simply decode the session and hijack the information contained within the CookieStore and use it to access another user's account, among other things!

After Rails 4, the vulnerable CookieStore became the hardened EncryptedCookieStore. The sessions stored on the client became encrypted by SHA1. They could no longer be easily tampered with. Within config/secrets.yml you can see your SECRET_KEY_BASE; these keys are to be kept secret and safe, as they are used to unlock session related information.

Simple Session Application

Here's what we're going to do:

  • Regale on static marketing pages.
  • Explore our sample application.
  • Connect our application to Fly.
  • Configure the Rails 4+ Session Routing Middleware.

Our application is a simple Rails application with login and logout. When you log in to the sample application and click 'Remember me', an encrypted session is stored containing your unique userId. If you want to tinker along with this article, the source code for our application is available here.

The demo is live, too, if you'd like to play with it; when you login, note the appearance of the encrypted cookies.

cookie

Fly!

Fly is designed to help you deliver applications. As we revealed in the introduction, we are going to use it to intelligently route between our marketing page and our Rails application. Our example site is wizardry.ca, a Symposium of Wizardly Things! There's a very 90s marketing page available at the root domain.

Static sites make excellent marketing pages. GitHub Pages, for example, can provide a simple and free way of creating and hosting a static site; wizardry.ca is hosted using GitHub Pages.

If you need a hand configuring a static page for this example, no problem: we have a guide for you.

Fly demonstration, showing example.com being the front for both Rails and marketing page

When our users are logged in and choose to be remembered, we want our application to live on the "root" domain, wizardry.ca/.

However, given that the root is already occupied by our marketing page, we'll need to mount the application onto its own subfolder: wizardry.ca/app/.

We'll need to add both GitHub pages and our application to Fly; we start by signing up and creating a site along with a GitHub Pages backend.

Now, let's add our Rails application and connect wormhole to Heroku. The application portion of wizardry.ca is hosted using Heroku. Within Fly, visit your site, click Routing, then Add Backend within the Backends tab. We'll choose Heroku then fill in our repository name.

Our next step is to add the Fly Buildpack and the FLY_TOKEN to our Heroku application; this will install wormhole for end-to-end encryption and better global load balancing.

We can do this via the command line...

heroku buildpacks:add https://github.com/superfly/fly-heroku-buildpack
heroku config:set FLY_TOKEN=5fab46327d83364af3125c039738740c1e021de55439d4317444fe7b7be5fea5

Now, push to Heroku. Your Rails application is now connected to Fly - fantastic!

Once we push our changes up and Heroku builds, we can visit wizardry.ca/app/signup and wizardry.ca/app/login to see our application's sign up and login pages as we expect them. Our cookie-based routing hasn't been setup yet. With both our static marketing page and our application mounted we can get into the Fly routing magic.

Route-and-About

Head to the Middleware tab within your site and enable the Rails 4+ Session Routing Middleware. You'll be prompted to put in two pieces of information: the Session Cookie Name and the Session Cookie Encryption Key. Given that your client-side Rails sessions are encrypted, we'll need to know the name of the session and the safely kept value of the SECRET_KEY_BASE.

Fly stores this information securely. Providing Fly with the SECRET_KEY_BASE gives our edge-servers the ability to peek into the cookie and route requests based on their contents. We added wormhole earlier; wormhole creates an encrypted tunnel between our edge-server and your application. Your data is secured over HTTPS to the edge-server and then through encrypted tunnels directly to your application: end-to-end encryption.

Once the Middleware has been enabled, we can use the HTTP Header field within Routing Rules. Each key stored in the session cookie can be represented via an HTTP Header. For example, userId: becomes Fly-User-Id; the former is an application-bound key and we can use its latter translation for routing -- fancy!

Let's see this in action.

In Fly, head to your site, visit Routing, then click Add routing rule.

To make us more comfortable with what we're setting, we'll do a breakdown of the form fields:

Priority: When the route will apply itself. For this route, we want the priority to be higher (less than) the priority of our usual / route.

Request Scheme: We can specify HTTP or HTTPS, or all. We'd like all values.

Hostname: If we have multiple hostnames, we can specify them or match on all of them. This is useful if you're applying a subdomain or a separate TLD.

HTTP Header: With Middleware enabled, all our JSON-ified session cookie fields can be turned into headers; userId becomes Fly-User-Id.

HTTP Header Content to Match: We can use regex to match for any permutation that appears as a header value. For our demo, we're using \d+ which is any digit greater than one. It's a simple way of asking: do you have a sessionID?

Path to Match: Requests to this path will search for a matching HTTP Header. We want it to be our root domain, wizardry.ca, so the value should be /.

Route to Backend: Our Heroku application will appear here. Select it!

Path Rewrite: We do not need this field in our example. You'd use it to manipulate the path that the server is expecting.

Cool. To conclude, we'll weave everything together.

When we visit wizardry.ca, we'll see our marketing site. Now, head to wizardry.ca/app/signup or wizardry.ca/app/login. After login or signup, our application is redirected back to wizardry.ca/ instead of living on the wizardry.ca/app subfolder.

Try it out! Sign up, log in, then notice that ``wizardry.ca and its glorious purple-y-ness has been replaced by our application's 'Logged in!' page. Logout or delete your cookies and the wizards are back.

Summary

We voyaged through one excellent use-case for the Fly Rails 4+ Session Middleware: serving your application from / when a userId is detected and your marketing page when one is not. With the ability to create HTTP headers based on your secured session cookie values, you can route on gnarly cookie patterns of all sorts.

Did the user save a device preference? Send them mobile-optimized pages. Have they visited these features, read those articles? Route this way! Is a language set? Send them regionalized content.

If you'd like to get deep into the guts of Lua and OpenResty, you can cook something like this yourself. If that's not what you're into right now, check out Fly. It's free to sign up, as are your first 2,000,000 requests and 100GB of transfer.

Kellen Evan Person

@thegoodroot

Kellen Evan Person

A polite, forest-dwelling Canadian who enjoys coding and writing. He's spent near two decades building web applications and strives to keep development fun and light-hearted.

North Vancouver, Canada