Heroku Bouncer
Heroku Bouncer is a Rack middleware (implemented in Sinatra) that requires Heroku OAuth on all requests.
Ruby and Rack compatibility
-
Ruby: Versions
>= 0.8.0
require Ruby >= 2.2. If you need a version that works with prior versions of Ruby, please use version~> 0.7.1
. Note, however, that0.7.1
does not support Rack 2 (Rails 5+). -
Rack: Rack 1 and 2 are supported.
Demo
heroku-bouncer-demo is a Sinatra app that uses heroku-bouncer.
Use
-
Install the Heroku OAuth CLI plugin.
console heroku plugins:install heroku-cli-oauth
-
Create your OAuth client using
/auth/heroku/callback
as your callback endpoint. Usehttp://localhost:5000/auth/heroku/callback
for local development with Foreman.console 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.
-
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. Inconfig.ru
:```ruby 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.ruby 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
:ruby config.middleware.use ::Heroku::Bouncer, oauth: { id: "...", secret: "..." }, secret: "..."
-
Fill in the required settings
:oauth
and:secret
as explained below.
Settings
Two settings are required:
oauth
: Your OAuth credentials as a hash -:id
and:secret
.secret
: A random string used as an encryption secret used to secure the user information in the session.
Using environment variables for these is recommended, for example:
ruby
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:
oauth[:scope]
: The [OAuth scope][] to use when requesting the OAuth token. Default:identity
.allow_if
: A lambda that takes an email address. If the lambda evaluates to true, allow the user through. If false, redirects toredirect_url
. By default, all users are allowed through after authenticating.allow_if_user
: A lambda that takes the account resource representing the user. If the lambda evaluates to true, allow the user through. If false, redirects toredirect_url
. By default, all users are allowed through after authenticating.login_path
: Where unauthorized users are redirected so they can be prompted to login. Defaults toheroku-bouncer
’s own/auth/login
path.redirect_url
: Where unauthorized users are redirected during the OAuth callback phase. Defaults towww.heroku.com
.expose_token
: Expose the OAuth token in the session, allowing you to make API calls as the user. Default:false
expose_email
: Expose the user’s email address in the session. Default:true
expose_user
: Expose the user attributes in the session. Default:true
session_sync_nonce
: If present, determines the name of a cookie shared across properties under a same domain in order to keep their sessions synchronized. Default:nil
allow_anonymous
: Accepts a lambda that gets called with each request. If the lambda evals to true, the request will not enforce authentication (e.g:allow_anonymous: lambda { |req| !/\A\/admin/.match(req.fullpath) }
will allow anonymous requests except those with under the/admin
path). Default:nil
, which does not allow anonymous access to any URL.skip
: Accepts a lambda that gets called with each request’senv
. If the lambda gets evaluated to true, heroku-bouncer’s middleware will be completely skipped. Default: ‘false’, which applies heroku-bouncer to all requests.
You use these by passing a hash to the use
call, for example:
ruby
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:
```erb