I want to use OmniAuth to retrieve user access_token and secret from facebook , twitter and google at the same time.
我想同时使用OmniAuth从facebook、twitter和谷歌检索用户access_token和secret。
I'm using Devise for authentication , and I want to know how to request keys when the user is signed in and store them in a database to use them later .
我正在使用devices进行身份验证,我想知道如何在用户登录时请求密钥,并将它们存储在数据库中以便以后使用。
1 个解决方案
#1
6
Add in gem file
gem文件中添加
gem 'devise'
gem 'omniauth'
gem 'omniauth-twitter'
gem 'omniauth-facebook'
gem 'omniauth-linkedin'
Generate migrations and models
生成迁移和模型
rails generate devise:install
rails generate devise user
rails g migration add_name_to_users name:string
rails g model identity user:references provider:string uid:string
app/models/identity.rb
应用程序/模型/ identity.rb
class Identity < ActiveRecord::Base
belongs_to :user
validates_presence_of :uid, :provider
validates_uniqueness_of :uid, :scope => :provider
def self.find_for_oauth(auth)
find_or_create_by(uid: auth.uid, provider: auth.provider)
end
end
app/config/initializers/devise.rb
app / config /初始化/ devise.rb
Devise.setup do |config|
...
config.omniauth :facebook, "KEY", "SECRET"
config.omniauth :twitter, "KEY", "SECRET"
config.omniauth :linked_in, "KEY", "SECRET"
...
end
config/environments/[environment].rb
配置/环境/(环境). rb
# General Settings
config.app_domain = 'somedomain.com'
# Email
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.default_url_options = { host: config.app_domain }
config.action_mailer.smtp_settings = {
address: 'smtp.gmail.com',
port: '587',
enable_starttls_auto: true,
user_name: 'someuser',
password: 'somepass',
authentication: :plain,
domain: 'somedomain.com'
}
config/routes.rb
配置/ routes.rb
devise_for :users, :controllers => { omniauth_callbacks: 'omniauth_callbacks' }
app/controllers/omniauth_callbacks_controller.rb
app / controllers / omniauth_callbacks_controller.rb
Therefore, to link accounts with multiple providers the current_user session must be already set when the OAuth callback returns, and passed to User.find_for_oauth. This might sound complicated, but all thats required to link a different provider, Facebook for example, is to redirect_to user_omniauth_authorize_path(:facebook) while the user is already logged in
因此,要与多个提供者链接帐户,必须在OAuth回调返回时设置current_user会话,并将其传递给User.find_for_oauth。这听起来可能很复杂,但是连接另一个提供者(例如Facebook)所需要的一切就是在用户已经登录时将其重定向到user_omniauth_authorize_path(: Facebook)
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
@user = User.find_for_oauth(env["omniauth.auth"], current_user)
if @user.persisted?
sign_in_and_redirect @user, event: :authentication
set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
else
session["devise.#{provider}_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
}
end
[:twitter, :facebook, :linked_in].each do |provider|
provides_callback_for provider
end
def after_sign_in_path_for(resource)
if resource.email_verified?
super resource
else
finish_signup_path(resource)
end
end
end
app/models/user.rb
应用程序/模型/ user.rb
class User < ActiveRecord::Base
TEMP_EMAIL_PREFIX = 'change@me'
TEMP_EMAIL_REGEX = /\Achange@me/
# Include default devise modules. Others available are:
# :lockable, :timeoutable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
validates_format_of :email, :without => TEMP_EMAIL_REGEX, on: :update
def self.find_for_oauth(auth, signed_in_resource = nil)
# Get the identity and user if they exist
identity = Identity.find_for_oauth(auth)
# If a signed_in_resource is provided it always overrides the existing user
# to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date.
user = signed_in_resource ? signed_in_resource : identity.user
# Create the user if needed
if user.nil?
# Get the existing user by email if the provider gives us a verified email.
# If no verified email was provided we assign a temporary email and ask the
# user to verify it on the next step via UsersController.finish_signup
email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
email = auth.info.email if email_is_verified
user = User.where(:email => email).first if email
# Create the user if it's a new registration
if user.nil?
user = User.new(
name: auth.extra.raw_info.name,
#username: auth.info.nickname || auth.uid,
email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
password: Devise.friendly_token[0,20]
)
user.skip_confirmation!
user.save!
end
end
# Associate the identity with the user if needed
if identity.user != user
identity.user = user
identity.save!
end
user
end
def email_verified?
self.email && self.email !~ TEMP_EMAIL_REGEX
end
end
config/routes.rb
match '/users/:id/finish_signup' => 'users#finish_signup', via: [:get, :patch], :as => :finish_signup
app/controllers/users_controller.rb
app / controllers / users_controller.rb
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
...
# GET /users/:id.:format
def show
# authorize! :read, @user
end
# GET /users/:id/edit
def edit
# authorize! :update, @user
end
# PATCH/PUT /users/:id.:format
def update
# authorize! :update, @user
respond_to do |format|
if @user.update(user_params)
sign_in(@user == current_user ? @user : current_user, :bypass => true)
format.html { redirect_to @user, notice: 'Your profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
# GET/PATCH /users/:id/finish_signup
def finish_signup
# authorize! :update, @user
if request.patch? && params[:user] #&& params[:user][:email]
if @user.update(user_params)
@user.skip_reconfirmation!
sign_in(@user, :bypass => true)
redirect_to @user, notice: 'Your profile was successfully updated.'
else
@show_errors = true
end
end
end
# DELETE /users/:id.:format
def destroy
# authorize! :delete, @user
@user.destroy
respond_to do |format|
format.html { redirect_to root_url }
format.json { head :no_content }
end
end
private
def set_user
@user = User.find(params[:id])
end
def user_params
accessible = [ :name, :email ] # extend with your own params
accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
params.require(:user).permit(accessible)
end
end
app/views/users/finish_signup.html.erb
app / views /用户/ finish_signup.html.erb
<div id="add-email" class="container">
<h1>Add Email</h1>
<%= form_for(current_user, :as => 'user', :url => finish_signup_path(current_user), :html => { role: 'form'}) do |f| %>
<% if @show_errors && current_user.errors.any? %>
<div id="error_explanation">
<% current_user.errors.full_messages.each do |msg| %>
<%= msg %><br>
<% end %>
</div>
<% end %>
<div class="form-group">
<%= f.label :email %>
<div class="controls">
<%= f.text_field :email, :autofocus => true, :value => '', class: 'form-control input-lg', placeholder: 'Example: email@me.com' %>
<p class="help-block">Please confirm your email address. No spam.</p>
</div>
</div>
<div class="actions">
<%= f.submit 'Continue', :class => 'btn btn-primary' %>
</div>
<% end %>
</div>
app/controllers/application_controller.rb
app / controllers / application_controller.rb
The following method is optional, but it’s useful if you want to ensure the user has provided all the necessary information before accessing a specific resource.
下面的方法是可选的,但是如果您想要确保用户在访问特定资源之前提供了所有必要的信息,那么它是非常有用的。
You can use it in a before_filter like so: before_filter :ensure_signup_complete, only: [:new, :create, :update, :destroy]
您可以在before_filter中使用它,比如:before_filter:ensure_signup_complete,只有:[:new,:create,:update,:destroy]
class ApplicationController < ActionController::Base
...
def ensure_signup_complete
# Ensure we don't go into an infinite loop
return if action_name == 'finish_signup'
# Redirect to the 'finish_signup' page if the user
# email hasn't been verified yet
if current_user && !current_user.email_verified?
redirect_to finish_signup_path(current_user)
end
end
end
#1
6
Add in gem file
gem文件中添加
gem 'devise'
gem 'omniauth'
gem 'omniauth-twitter'
gem 'omniauth-facebook'
gem 'omniauth-linkedin'
Generate migrations and models
生成迁移和模型
rails generate devise:install
rails generate devise user
rails g migration add_name_to_users name:string
rails g model identity user:references provider:string uid:string
app/models/identity.rb
应用程序/模型/ identity.rb
class Identity < ActiveRecord::Base
belongs_to :user
validates_presence_of :uid, :provider
validates_uniqueness_of :uid, :scope => :provider
def self.find_for_oauth(auth)
find_or_create_by(uid: auth.uid, provider: auth.provider)
end
end
app/config/initializers/devise.rb
app / config /初始化/ devise.rb
Devise.setup do |config|
...
config.omniauth :facebook, "KEY", "SECRET"
config.omniauth :twitter, "KEY", "SECRET"
config.omniauth :linked_in, "KEY", "SECRET"
...
end
config/environments/[environment].rb
配置/环境/(环境). rb
# General Settings
config.app_domain = 'somedomain.com'
# Email
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.default_url_options = { host: config.app_domain }
config.action_mailer.smtp_settings = {
address: 'smtp.gmail.com',
port: '587',
enable_starttls_auto: true,
user_name: 'someuser',
password: 'somepass',
authentication: :plain,
domain: 'somedomain.com'
}
config/routes.rb
配置/ routes.rb
devise_for :users, :controllers => { omniauth_callbacks: 'omniauth_callbacks' }
app/controllers/omniauth_callbacks_controller.rb
app / controllers / omniauth_callbacks_controller.rb
Therefore, to link accounts with multiple providers the current_user session must be already set when the OAuth callback returns, and passed to User.find_for_oauth. This might sound complicated, but all thats required to link a different provider, Facebook for example, is to redirect_to user_omniauth_authorize_path(:facebook) while the user is already logged in
因此,要与多个提供者链接帐户,必须在OAuth回调返回时设置current_user会话,并将其传递给User.find_for_oauth。这听起来可能很复杂,但是连接另一个提供者(例如Facebook)所需要的一切就是在用户已经登录时将其重定向到user_omniauth_authorize_path(: Facebook)
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
@user = User.find_for_oauth(env["omniauth.auth"], current_user)
if @user.persisted?
sign_in_and_redirect @user, event: :authentication
set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
else
session["devise.#{provider}_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
}
end
[:twitter, :facebook, :linked_in].each do |provider|
provides_callback_for provider
end
def after_sign_in_path_for(resource)
if resource.email_verified?
super resource
else
finish_signup_path(resource)
end
end
end
app/models/user.rb
应用程序/模型/ user.rb
class User < ActiveRecord::Base
TEMP_EMAIL_PREFIX = 'change@me'
TEMP_EMAIL_REGEX = /\Achange@me/
# Include default devise modules. Others available are:
# :lockable, :timeoutable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
validates_format_of :email, :without => TEMP_EMAIL_REGEX, on: :update
def self.find_for_oauth(auth, signed_in_resource = nil)
# Get the identity and user if they exist
identity = Identity.find_for_oauth(auth)
# If a signed_in_resource is provided it always overrides the existing user
# to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date.
user = signed_in_resource ? signed_in_resource : identity.user
# Create the user if needed
if user.nil?
# Get the existing user by email if the provider gives us a verified email.
# If no verified email was provided we assign a temporary email and ask the
# user to verify it on the next step via UsersController.finish_signup
email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
email = auth.info.email if email_is_verified
user = User.where(:email => email).first if email
# Create the user if it's a new registration
if user.nil?
user = User.new(
name: auth.extra.raw_info.name,
#username: auth.info.nickname || auth.uid,
email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
password: Devise.friendly_token[0,20]
)
user.skip_confirmation!
user.save!
end
end
# Associate the identity with the user if needed
if identity.user != user
identity.user = user
identity.save!
end
user
end
def email_verified?
self.email && self.email !~ TEMP_EMAIL_REGEX
end
end
config/routes.rb
match '/users/:id/finish_signup' => 'users#finish_signup', via: [:get, :patch], :as => :finish_signup
app/controllers/users_controller.rb
app / controllers / users_controller.rb
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
...
# GET /users/:id.:format
def show
# authorize! :read, @user
end
# GET /users/:id/edit
def edit
# authorize! :update, @user
end
# PATCH/PUT /users/:id.:format
def update
# authorize! :update, @user
respond_to do |format|
if @user.update(user_params)
sign_in(@user == current_user ? @user : current_user, :bypass => true)
format.html { redirect_to @user, notice: 'Your profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
# GET/PATCH /users/:id/finish_signup
def finish_signup
# authorize! :update, @user
if request.patch? && params[:user] #&& params[:user][:email]
if @user.update(user_params)
@user.skip_reconfirmation!
sign_in(@user, :bypass => true)
redirect_to @user, notice: 'Your profile was successfully updated.'
else
@show_errors = true
end
end
end
# DELETE /users/:id.:format
def destroy
# authorize! :delete, @user
@user.destroy
respond_to do |format|
format.html { redirect_to root_url }
format.json { head :no_content }
end
end
private
def set_user
@user = User.find(params[:id])
end
def user_params
accessible = [ :name, :email ] # extend with your own params
accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
params.require(:user).permit(accessible)
end
end
app/views/users/finish_signup.html.erb
app / views /用户/ finish_signup.html.erb
<div id="add-email" class="container">
<h1>Add Email</h1>
<%= form_for(current_user, :as => 'user', :url => finish_signup_path(current_user), :html => { role: 'form'}) do |f| %>
<% if @show_errors && current_user.errors.any? %>
<div id="error_explanation">
<% current_user.errors.full_messages.each do |msg| %>
<%= msg %><br>
<% end %>
</div>
<% end %>
<div class="form-group">
<%= f.label :email %>
<div class="controls">
<%= f.text_field :email, :autofocus => true, :value => '', class: 'form-control input-lg', placeholder: 'Example: email@me.com' %>
<p class="help-block">Please confirm your email address. No spam.</p>
</div>
</div>
<div class="actions">
<%= f.submit 'Continue', :class => 'btn btn-primary' %>
</div>
<% end %>
</div>
app/controllers/application_controller.rb
app / controllers / application_controller.rb
The following method is optional, but it’s useful if you want to ensure the user has provided all the necessary information before accessing a specific resource.
下面的方法是可选的,但是如果您想要确保用户在访问特定资源之前提供了所有必要的信息,那么它是非常有用的。
You can use it in a before_filter like so: before_filter :ensure_signup_complete, only: [:new, :create, :update, :destroy]
您可以在before_filter中使用它,比如:before_filter:ensure_signup_complete,只有:[:new,:create,:update,:destroy]
class ApplicationController < ActionController::Base
...
def ensure_signup_complete
# Ensure we don't go into an infinite loop
return if action_name == 'finish_signup'
# Redirect to the 'finish_signup' page if the user
# email hasn't been verified yet
if current_user && !current_user.email_verified?
redirect_to finish_signup_path(current_user)
end
end
end