Devise::Capturable
is a gem that makes it possible to use the Janrain Engage user registration widget as a login system, while still having a Devise authentication setup with a Rails User
model.
It can be used right out of the box with automatic user creation, or can be configured to show a "second step form", to add extra values to your user model on top of Janrain.
In the following I use the name User
for the Devise user model, but it will work with any Devise-enabled model.
This gem will replace the normal Devise registrations
and sessions
views with the Janrain user registration widget. The flow is as follows.
- User clicks a login link
- User is presented with the user registration widget and either registers or logs in
- The gem automatically listens to the widget's
onCaptureLoginSuccess
event, grabs the oauth code, and makes aPOST
request with the OAuth code to thesessions_controller
- The gem will grab the oauth token in the
session_controller
, load the user data from the Capture API (to ensure the validity of the token), and either create a new user or log in the user if the user already exists in the Rails DB.
You will need to perform these steps to setup the gem.
gem "devise_capturable"
class User < ActiveRecord::Base
devise ..., :capturable
end
In your config/initializers/devise.rb
initializer, add the following settings.
Devise.setup do |config|
config.capturable_server = "https://myapp.janraincapture.com"
config.capturable_client_id = "myclientid"
config.capturable_client_secret = "myclientsecret"
config.capturable_redirect_uri = "http://sample.com" # Optional, see below
config.capturable_auto_create_account = false # Optional, see below
config.capturable_redirect_if_no_user = "/users/sign_up" # Optional, see below
end
The gem ships with a JS file that automatically subscribes to the login event and makes a POST to the sessions_controller
. Make sure to include it in you asset pipeline.
//= require devise_capturable
Now add the script provided by Janrain with janrainDefaultSettings()
, janrainInitLoad()
, and all the HTML template strings to your application layout. You might need to include a different script in your development environment.
Keep in mind that this script does not need to include any onCaptureLoginSuccess
event listeners or other event code. The gem will handle that for you.
<html>
<head>
...
<script type="text/javascript" id="janrainCaptureDevScript">
// YOUR CODE HERE
</script>
</head>
<body>
...
</body>
</html>
Now add the Janrain CSS to your asset pipeline. Simply copy janrain.css
and janrain-mobile.css
to app/assets/stylesheets
.
The gem ships with a helper method to show a link that opens the widget. You will use this to show the login / registration form, but use the normal destroy_user_session_path
helper to log out the user. This is because Devise, not Capture, is controlling the user cookie.
<ul class="nav">
<% if user_signed_in? %>
<li><%= link_to 'Logout', destroy_user_session_path, :method=>'delete' %></li>
<% else %>
<li><%= link_to_capturable "Sign In / Sign Up" %></li>
<% end %>
</ul>
That's it!
By default Devise will now create a database record when the user logs in for the first time. On following logins, the user will just be logged in. The only property Devise will save in the user model is the email
address provided by Capture. You can however change this (See "Changing Defaults")
The Janrain User Registration widget relies on settings that are 1) never used and 2) breaks the widget if they are not present. To circumvent this madness, this gem will automatically set a bunch of janrain setting variables for you:
// default settings for gem
janrain.settings.capture.flowName = 'signIn';
janrain.settings.capture.responseType = 'code';
janrain.settings.capture.redirectUri = 'http://stupidsettings.com';
You can delete these settings from your embed code, as the gem will set them for you. Remember that you still need a tokenUrl
setting with a whitelisted URL, even though this setting is never used either.
There are times where you might want to save more than the email
of your user in the Rails User
model. You can override the before_capturable_create
instance method to do this. Here's an example where I'm also saving the uuid
. The capture_data
parameter passed to the function is the Janrain Capture entity
JSON result that has a bunch of information about the user, and the params
parameter is the controller parameters.
class User < ActiveRecord::Base
devise ..., :capturable
def before_capturable_create(capture_data, params)
self.email = capture_data["email"]
self.uuid = capture_data["uuid"]
end
end
You might also want to update your Rails model if the data changes on the Janrain side. You can use the before_capturable_sign_in
method for this. This method is empty by default, but can be defined in your model to update certain attributes. Here's an example where we update the fake "is_awesome" attribute.
class User < ActiveRecord::Base
devise ..., :capturable
def before_capturable_sign_in(capture_data, params)
self.is_awesome = capture_data["is_awesome"]
self.save!
end
end
When a user logs in, Devise will call the Capture API and try to find a user with the email returned by the API. You can change this by overriding the find_with_capturable_params
class method in your User
model. Here's an example where I'm telling Devise to find the user by the uuid
instead.
def self.find_with_capturable_params(capture_data)
self.find_by_uuid(capture_data["uuid"])
end
By default the gem will create a user if the user doesn't exist in the system. If you want to disable the creation of new users, and only allow current users to log in, you can disable automatic account creation in your config/intitializers/devise.rb
initializer.
Devise.setup do |config|
config.capturable_auto_create_account = false
end
There are a number of reasons you may need to override the default janrainCaptureWidgetOnLoad
Javascript function:
- To use the
signInEmbedded
flow instead of the defaultsignIn
- To specify a
redirectUri
value (required to use Janrain's Federate feature) - To add functionality to Janrain event handlers, or add more event handlers
To do so, you'll need to write your own janrainCaptureWidgetOnLoad
function and include it separately. In this example, I'm replacing the default function with my own, included inline when loading the Janrain widget:
function janrainCaptureWidgetOnLoad() {
janrain.settings.capture.flowName = 'signInEmbedded';
janrain.settings.capture.responseType = 'code';
janrain.settings.capture.redirectUri = 'http://mydomain.com';
janrain.capture.ui.start();
// afterJanrainLogin is provided by Devise::Capturable to assist with
// server-side login. It automatically makes a POST request to /users/sign_in if not configured.
janrain.events.onCaptureLoginSuccess.addHandler(afterJanrainLogin);
janrain.events.onCaptureRegistrationSuccess.addHandler(afterJanrainLogin);
};
Finally, I can also provide an optional path to send the Janrain authorizationCode
to in my application, if using a Devise resource not named User
:
janrain.events.onCaptureRegistrationSuccess.addHandler(function(result) {
afterJanrainLogin(result, '<%= new_admin_session_path %>', "get");
});
Run the tests with the rspec
command.
- Ability to add the Janrain script to the asset pipeline. However, it relies on an 'id', which makes it harder.
- Remove normal Devise sign_in routes? Or leave it up to the user?