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.
No! Not those cookies. These cookies:
Before Rails 4, Rails had an object known as 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.
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.
When our users are logged in and choose to be remembered, we want our application to live on the "root" domain,
However, given that the root is already occupied by our marketing page, we'll need to mount the application onto its own subfolder:
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.
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
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,
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
Request Scheme: We can specify
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;
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
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.
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. Fly started when we wondered "what would a programmable edge look like"? Developer workflows work great for infrastructure like CDNs and optimization services. You should really see for yourself, though.