Home

Awesome

Build Status

Heroku Bouncer

Heroku Bouncer is a Rack middleware (implemented in Sinatra) that requires Heroku OAuth on all requests.

Ruby and Rack compatibility

Demo

heroku-bouncer-demo is a Sinatra app that uses heroku-bouncer.

Use

  1. Install the Heroku OAuth CLI plugin.

    heroku plugins:install heroku-cli-oauth
    
  2. Create your OAuth client using /auth/heroku/callback as your callback endpoint. Use http://localhost:5000/auth/heroku/callback for local development with Foreman.

    heroku clients:create localhost http://localhost:5000/auth/heroku/callback
    heroku clients:create myapp https://myapp.herokuapp.com/auth/heroku/callback
    

    See https://github.com/heroku/heroku-cli-oauth#clients for more details.

  3. Configure the middleware as follows:

    Rack

    Heroku::Bouncer requires a session middleware to be mounted above it. Pure Rack apps will need to add such a middleware if they don't already have one. In config.ru:

    require 'rack/session/cookie'
    require 'heroku/bouncer'
    require 'my_app'
    
    # use `openssl rand -base64 32` to generate a secret
    use Rack::Session::Cookie, secret: "...", key: "my_app_session"
    use Heroku::Bouncer,
      oauth: { id: "...", secret: "..." }, secret: "..."
    run MyApp
    

    Sinatra

    Heroku::Bouncer can be run like a Rack app, but since a Sinatra app can mount Rack middleware, it may be easier to mount it inside the app and use Sinatra's session.

    class MyApp < Sinatra::Base
      ...
      enable :sessions, secret: "...", key: "my_app_session"
      use ::Heroku::Bouncer,
        oauth: { id: "...", secret: "..." }, secret: "..."
      ...
    

    Rails

    Add a middleware configuration line to config/application.rb:

    config.middleware.use ::Heroku::Bouncer,
      oauth: { id: "...", secret: "..." }, secret: "..."
    
  4. Fill in the required settings :oauth and :secret as explained below.

Settings

Two settings are required:

Using environment variables for these is recommended, for example:

use Heroku::Bouncer,
  oauth: { id: ENV['HEROKU_OAUTH_ID'], secret: ENV['HEROKU_OAUTH_SECRET'] },
  secret: ENV['HEROKU_BOUNCER_SECRET']

Here are the supported options you can pass to the middleware:

You use these by passing a hash to the use call, for example:

use Heroku::Bouncer,
  oauth: { id: "...", secret: "...", scope: "global" },
  secret: "...",
  expose_token: true

Prompt to Login

To mitigate Cross-Site Request Forgery attacks, OmniAuth no longer allows GET requests to the /auth/heroku path. To support this, heroku-bouncer no longer redirects unauthorized requests to the /auth/heroku path. Instead users are redirected to /auth/login where a simple HTML template is rendered, prompting the user to authenticate with Heroku.

The template includes a <form> with a button which will POST to the /auth/heroku path. It also includes the Authenticity Token from Rack::Protection. The view provides no styling; it is the most basic example of what's required.

To render your own prompt UI, provide the :login_path option. Unauthenticated users will be redirected to this path, allowing you to control the UI. The resulting page should render an HTML <form> which will POST to the /auth/heroku path. The form needs to include a field named authenticity_token with the token from Rack::Protection.

An example to get you started:

<form method="post" action="/auth/heroku">
  <input type="hidden" name="authenticity_token" value="<%= request.env["rack.session"]["csrf"] %>">
<button type="submit">Sign in via Heroku</button>

How to get the data

Based on your choice of the expose options above, the middleware adds the following keys to your request environment:

You can access this in Sinatra and Rails by request.env[key], e.g. request.env['bouncer.token'].

Using the Heroku API

If you set expose_token to true, you'll get an OAuth token that you can use to make Heroku API calls on behalf of the logged-in user using the platform API.

heroku = PlatformAPI.connect_oauth(request.env["bouncer.token"])
info = heroku.app.info('sushi')

Keep in mind that this adds substantial security risk to your application.

The API token is short-lived, and expires 8 hours after issue. Heroku provides a separate refresh_token (available as bouncer.refresh_token) that can be used to fetch fresh API tokens if necessary. See the token refresh documentation for details.

Logging out

Send users to /auth/sso-logout if logging out of Heroku is appropriate, or /auth/logout if you only wish to logout of your app. The latter will redirect to /, which may result is the user being logging in again.

Security Model: A Tale of Three Secrets

There are three secrets in use:

In totality, Heroku Bouncer ensures session data can only be generated and read by your application. However, they do not protect against replay attacks if the data is obtained in its entirety. SSL and session timeouts should be used to help mitigate this risk. CSRF tokens for any actions that modify data are also recommended.

Related Documentation