duke-libraries.github.io

The Blue Devil is in the details

Authentication with Devise, Omniauth and Shibboleth

11 Feb 2015 David Chandek-Stark

In planning to roll out a public access Hydra head for our repository, we had these authentication and authorization requirements:

Background

In developing our first Hydra head, an administrative application for Library staff, the requirements were simpler:

Since we would not permit anonymous access to the admin app, we could simply force Shibboleth authentication in the web server and then auto-login in Rails if the REMOTE_USER environment variable was set. To accomplish this result with Devise as the authentication framework, I developed a plugin, devise-remote-user. Using Devise’s and Warden’s failover capability, authentication in development and test would fallback to the database authenticatable strategy. Done.

Initially, to deal the public access case I attempted to modify devise-remote-user, but eventually realized that I would essentially have to reproduce what Devise + Omniauth provides. And fortunately, there is a fine Shibboleth strategy for Omniauth already available. The complication I discovered is that Devise/Omniauth sort of assumes that Omniauth provider logins are presented as options on the standard user/password login form, unless omniauthable is the only authentication strategy you’re using. In our case, I wanted the Shibboleth login to be automatic and seamless in production (on the server).

Solution

The Rails part

class Users::SessionsController < Devise::SessionsController

  def new
    store_location_for(:user, request.referrer) # return to previous page after authn
    if Ddr::Auth.require_shib_user_authn
      # don't want the "sign in or sign up" flash alert
      # which is set by the Devise failure app
      flash.discard(:alert)
      redirect_to user_omniauth_authorize_path(:shibboleth)
    else
      super
    end
  end

  def after_sign_out_path_for(scope)
    return Ddr::Auth.sso_logout_url if Ddr::Auth.require_shib_user_authn
    super
  end

end
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  
  def shibboleth
    # had to create the `from_omniauth(auth_hash)` class method on our User model
    user = resource_class.from_omniauth(request.env["omniauth.auth"])
    set_flash_message :notice, :success, kind: "Duke NetID"
    sign_in_and_redirect user
  end

end
class FailureApp < Devise::FailureApp

  def respond
    if scope == :user && Ddr::Auth.require_shib_user_authn
      store_location!
      redirect_to user_omniauth_authorize_path(:shibboleth)
    else
      super
    end
  end

end

Don’t forget to configure the Warden failure app in the Devise initializer.

The Apache part

<Directory /path/to/rails/app/public>
  Options -MultiViews
  AuthName "NetID"
  AuthType Shibboleth
  # Permits anonymous access
  ShibRequestSetting requireSession 0
  Require shibboleth
</Directory>

# user_omniauth_authorize_path(:shibboleth)                                                                        
<Location /users/auth/shibboleth>
  ShibRequestSetting requireSession 1
</Location>