---
title: How to Share a Session Between Sinatra and Rails
teaser: |
  Mount a Sinatra app via Rails routes or
  use `Rack::Builder` if you need a custom middleware stack
  for your Sinatra app.
tags: web,rails,sinatra
author: Joël Quenneville
published_on: 2014-05-30
---

As Rails developers, we run into Sinatra apps all the time: gems such as Resque
which expose a dashboard via Sinatra, legacy Sinatra apps that run alongside a
main Rails app, and Sinatra <abbr title="Application Programming
Interface">API</abbr>s embedded within a Rails app, to name a few examples.
Here's a common problem: how do you share authentication between the apps?

It would be really convenient to be able to do something like this in our
Sinatra app:

```ruby
get '/dashboard' do
  if session[:user_id].present?
    redirect to('/')
  else
    # set up and render dashboard
  end
end
```

Both Rails and Sinatra are Rack-based, which makes them play surprisingly well
together. They can be combined in two ways: via `Rack::Builder` or via the Rails
routes.

## Rack::Builder

This method treats Rails as just another Rack app. It creates a middleware stack
and mounts the apps at particular urls

```ruby
# config.ru
map '/api' do
  run MySinatraApp.new
end

map '/' do
  run MyRailsApp::Application.new
end
```

The standard way to handle authentication in Rails is via a session that is
stored in the client's browser via a cookie. This cookie is base64 encoded in
Rails 3.x and encrypted in Rails 4.x. In order to read and write from the
session, Rails uses a few middlewares. They (along with the other
middlewares that come by default with Rails) can be seen by running `rake
middleware`.

* ActionDispatch::Cookies
* ActionDispatch::Session::CookieStore
* Other middlewares

In order for the Sinatra app to be able to read/write from the Rails session,
it needs to have those two middlewares in its stack. The middleware needs
to know the name of the cookie that the session is stored in. Finally, the
middleware also needs to know the secret token used for signing and encrypting
the cookie

```ruby
# config.ru
map '/api' do
  use Rack::Config do |env| do
    env[ActionDispatch::Cookies::TOKEN_KEY] = MyRailsApp::Application.config.secret_token
  end
  use ActionDispatch::Cookies
  use ActionDispatch::Session::CookieStore, key: '_my_rails_app_session'
  run MySinatraApp.new
end

map '/' do
  run MyRailsApp::Application.new
end
```

## Rails routes

In the previous approach, both the Sinatra and Rails apps were first-class
citizens loaded via `config.ru`. An easier approach is to load all the Sinatra
apps via the Rails router. This automatically gives them access to the
middlewares loaded (and configured) by Rails.

```ruby
MyRailsApp::Application.routes.draw do
  mount MySinatraApp.new => '/api'
end
```

This works because the HTTP request travels through the Rails middleware stack
before reaching the router which then sends it to the proper app. When using
`config.ru`, the request is immediately routed to either the Sinatra app or the
Rails app so we need to manually add the middleware in front of the Sinatra app.

## Which approach to take

Mounting a Sinatra app via the Rails routes is the standard way to embed a
Sinatra app within a Rails app. Since they both share the same middleware
stack, you get shared sessions for free. However, if you need a custom
middleware stack for your Sinatra app then the `Rack::Builder` approach is the
way to go.

## Further reading

Learn more about [Rails and Rack](http://guides.rubyonrails.org/rails_on_rack.html)
