Heroku + Devise 1.2 + OmniAuth to GitHub = null value in column "email"
I'm trying to set up Devise 1.2 to allow user authentication via GitGub. As far as I can test it locally using Cucumber, and stubbing out GitHub OAuth, it seems to be working fine. After I deploy to Heroku, however, and try to authenticate, I get an error after being redirected back to my application from GitHub.
Checking the Heroku log, the error I see is...
PGError: ERROR: null value in column "email" violates not-null constraint
So apparently, I'm either not getting an email address 开发者_StackOverflowback from GitHub, or it's being lost somewhere along the way?
I've followed the instructions and examples on https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview and https://github.com/plataformatec/devise/wiki/OmniAuth:--Testing-%27facebook%27-signup--%5BRails-3---Cucumber---Capybara---Mongoid-%5D, but modified them for GitHub instead of Facebook, referring to the OmniAuth source code for the GitHub strategy at https://github.com/intridea/omniauth/blob/master/oa-oauth/lib/omniauth/strategies/github.rb.
Here's what I have for my devise_steps.rb file, which results in a passing Cucumber feature:
ACCESS_TOKEN = {
:access_token => "stevejdev"
}
# Not all pieces of this hash are yet proven to be correct.
# It does, at least supply the necessary information for a
# successful login simulation though.
GITHUB_INFO = {
:user => {
:id => '12345',
:email => 'johndoe@example.com',
# login value maps to nickname and to <user> part of
# "http://github.com/<user>" in urls[GitHub]
:login => 'johnxd',
:name => 'John Doe',
# blog value maps to urls[blog]
:blog => 'http://blaagstop.com/johndoe',
}
}
When /^GitHub replies$/ do
Devise::OmniAuth.short_circuit_authorizers!
Devise::OmniAuth.stub!(:github) do |b|
b.post('/login/oauth/access_token') {
[200, {}, ACCESS_TOKEN.to_json] }
b.get('/api/v2/json/user/show?access_token=stevejdev') {
[200, {}, GITHUB_INFO.to_json ] }
end
visit user_omniauth_callback_path(:github)
end
Here's my callback handler:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def github
@user = User.find_for_github_oauth(env["omniauth.auth"], current_user)
if @user.persisted?
flash[:notice] = I18n.t(
"devise.omniauth_callbacks.success", :kind => "GitHub" )
sign_in_and_redirect @user, :event => :authentication
else
session["devise.github_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
Here's the find_for_github_oauth definition in my User model:
def self.find_for_github_oauth(access_token, signed_in_resource=nil)
data = access_token['extra']['user_hash']
# Find the existing user or create a new one.
# Omit the password-generation code shown in the example at
# https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview
# because we're intending to use OmniAuth only, so
# presumably don't need a password.
User.find_by_email( data["email"] ) ||
User.create!( :email => data["email"] )
end
It turns out the problem is that GitHub does not use the email address as the user identifier, and does not necessarily include the email value in the token at all. I solved the problem by using the login instead of the email address.
Since I'm planning to support multiple providers in the future, I'm now using a combination of auth_provider
and login
fields as the logical key for the users
table, and I've specified config.authentication_keys = [ :auth_provider, :login ]
in the config/devise.rb file. When I'm adding support for another provider that does use an email address for the user's login, that's fine. I'll just use the email value from the provider's response as the login value for the model.
For posterity:
This issue has been resolved in the omniauth-github gem repo, in this merge.
It's not available in the latest version, so if you want to have it, you have to get the cutting-edge repo version by adding the repo address to your gemfile:
gem 'omniauth-github', :git => "git://github.com/intridea/omniauth-github.git"
精彩评论