Omniauth-Facebook:如何获得长期有效的访问令牌?
Omniauth-Facebook: How to get Long-Lived Access Tokens?
我跟随伟大的 Ryan Bates 帮助我设置 ominauth-facebook,但在他的教程中,他只展示了如何使用短期访问令牌(一小时或 2 小时)进行设置。我们如何修改他的教程以使用长期访问令牌(60 天)对其进行设置?
我一直在阅读 Facebook docs about it and this older SO question。
两个链接都建议添加一些这样的代码(尽管每个链接的代码略有不同):
GET /oauth/access_token?
grant_type=fb_exchange_token&
client_id={app-id}&
client_secret={app-secret}&
fb_exchange_token={short-lived-token}
将这个添加到哪里?
从我读到的内容来看,他们似乎让这件事变得比应该的更复杂。有人可以用初学者友好的语言来表达吗?
让我们开始个性化。
我的代码与 Bates 先生略有不同。
initializers/ominauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, "1540352575225959", "ee957abf5e851c98574cdfaebb1355f4", {:scope => 'user_about_me'}
end
routes.rb
get 'auth/:provider/callback', to: 'sessions#facebook'
sessions_controller
def facebook
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url
end
user.rb
class User < ActiveRecord::Base
acts_as_tagger
acts_as_taggable
has_many :notifications
has_many :activities
has_many :liked_comments, through: :comment_likes, class_name: 'Comment', source: :liked_comment
has_many :valuation_likes
has_many :habit_likes
has_many :goal_likes
has_many :stat_likes
has_many :comment_likes
has_many :authentications
has_many :habits, dependent: :destroy
has_many :levels
has_many :valuations, dependent: :destroy
has_many :comments
has_many :goals, dependent: :destroy
has_many :stats, dependent: :destroy
has_many :results, through: :stats
has_many :notes
accepts_nested_attributes_for :habits, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :notes, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :stats, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :results, :reject_if => :all_blank, :allow_destroy => true
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }, format: { with: /\A[a-z\sA-Z]+\z/,
message: "only allows letters" }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }, unless: -> { from_omniauth? }
has_secure_password
validates :password, length: { minimum: 6 }
def name
read_attribute(:name).try(:titleize)
end
def count_mastered
@res = habits.reduce(0) do |count, habit|
habit.current_level == 6 ? count + 1 : count
end
end
def count_challenged
@challenged_count = habits.count - @res
end
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.provider = auth.provider
user.image = auth.info.image
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.password = (0...8).map { (65 + rand(26)).chr }.join
user.email = (0...8).map { (65 + rand(26)).chr }.join+"@mailinator.com"
user.save!
end
end
def self.koala(auth)
access_token = auth['token']
facebook = Koala::Facebook::API.new(access_token)
facebook.get_object("me?fields=name,picture")
end
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Forgets a user. NOT SURE IF I REMOVE
def forget
update_attribute(:remember_digest, nil)
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
def good_results_count
results.good_count
end
# Follows a user.
def follow(other_user)
active_relationships.create(followed_id: other_user.id)
end
# Unfollows a user.
def unfollow(other_user)
active_relationships.find_by(followed_id: other_user.id).destroy
end
# Returns true if the current user is following the other user.
def following?(other_user)
following.include?(other_user)
end
private
def from_omniauth?
provider && uid
end
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase unless from_omniauth?
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
facebook.js.coffee.erb
jQuery ->
$('body').prepend('<div id="fb-root"></div>')
$.ajax
url: "#{window.location.protocol}//connect.facebook.net/en_US/all.js"
dataType: 'script'
cache: true
window.fbAsyncInit = ->
FB.init(appId: '<%= 1540372976229929 %>', cookie: true)
$('#sign_in').click (e) ->
e.preventDefault()
FB.login (response) ->
window.location = '/auth/facebook/callback' if response.authResponse
if $('#sign_out').length > 0
FB.getLoginStatus (response) ->
window.location = $('#sign_out').attr("href") if !response.authResponse
来源: omniauth-facebook。
"Being a good programmer is 3% talent and 97% not being distracted by the internet."
我在当前正在构建的应用程序中遇到了与您之前遇到的相同问题。我找到的解决方案建议使用 Koala 身份验证在其创建后立即延长初始的短期令牌。
对我来说效果很好的解决方案的改编如下...
首先,在您的 Gemfile 中包含 gem 'koala', '2.0.0'
。另外,请访问 Koala github site 了解更多信息。
现在让我们将上面的应用到user.rb的from_omniauth
方法...
def self.from_omniauth(auth)
# Sets 60 day auth token
oauth = Koala::Facebook::OAuth.new("1540352575225959", "ee957abf5e851c98574cdfaebb1355f4")
new_access_info = oauth.exchange_access_token_info auth.credentials.token
new_access_token = new_access_info["access_token"]
new_access_expires_at = DateTime.now + new_access_info["expires"].to_i.seconds
where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.provider = auth.provider
user.image = auth.info.image
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = new_access_token # auth.credentials.token <- your old token. Not needed anymore.
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.password = (0...8).map { (65 + rand(26)).chr }.join
user.email = (0...8).map { (65 + rand(26)).chr }.join+"@mailinator.com"
user.save!
end
end
附加信息:
我还注意到您正在为用户的电子邮件和密码生成一些虚拟值。我是否建议您使用 SecureRandom 库来这样做。虽然您的代码不太可能导致值冲突,但至少对于电子邮件,我建议将 (0...8).map { (65 + rand(26)).chr }.join+"@mailinator.com"
更改为更稳定的 SecureRandom.hex + "@mailinator.com"
。如果您使用电子邮件作为在应用程序中记录用户的方式,这一点尤其重要。
希望这能解决您的问题。祝你一切顺利,提姆。
更新:
很高兴我们设法回答了您的问题并一起解决了您的所有其他问题,但是这里有关于我之前谈到的重构代码的更新。 ..
考虑一种情况,通过 Facebook 登录的用户在您的网站上更改了一些关于他们自己的数据。假设他们更改了姓名、电子邮件或头像。这将正常工作 直到 同一用户尝试重新登录。将会发生的是 from_omniauth 方法将再次被触发并覆盖这些更改。这很糟糕,防止这种情况的方法是执行以下操作...
def self.from_omniauth(auth)
.
.
.
where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.provider = auth.provider
user.image = auth.info.image unless user.image != nil
user.uid = auth.uid
user.name = auth.info.name unless user.name != nil
user.oauth_token = new_access_token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.password = SecureRandom.urlsafe_base64 unless user.password != nil
user.email = SecureRandom.hex + "@mailinator.com" unless user.email != nil
user.activated = true
user.save!
end
关键是 unless user.image != nil
的使用确保只有当 :image
的初始值为 nil
时才会设置您的图像。如果说用户更改图像,则值不会是 nil
并且 from_omniauth 不会更改它。姓名、电子邮件和密码也是如此。不要将其设置为任何其他内容。
另请注意,我使用的是 SecureRandom.urlsafe_base64
而不是您的 (0...8).map { (65 + rand(26)).chr }.join
。这是因为 urlsafe_base64
生成一个随机 URL 安全的 base64 字符串,例如 i0XQ-7gglIsHGV2_BNPrdQ==
非常适合虚拟密码并且看起来也很优雅。
另一方面,我对电子邮件使用 hex
的原因是因为它会创建一个随机的十六进制字符串,例如 eb693ec8252cd630102fd0d0fb7c3485
,如果您碰巧有一些用于验证电子邮件的正则表达式(理想情况下你应该这样做)。
您可以使用 Koala::Facebook::OAuth
实例中的 exchange_access_token_info
方法
我跟随伟大的 Ryan Bates 帮助我设置 ominauth-facebook,但在他的教程中,他只展示了如何使用短期访问令牌(一小时或 2 小时)进行设置。我们如何修改他的教程以使用长期访问令牌(60 天)对其进行设置?
我一直在阅读 Facebook docs about it and this older SO question。
两个链接都建议添加一些这样的代码(尽管每个链接的代码略有不同):
GET /oauth/access_token?
grant_type=fb_exchange_token&
client_id={app-id}&
client_secret={app-secret}&
fb_exchange_token={short-lived-token}
将这个添加到哪里?
从我读到的内容来看,他们似乎让这件事变得比应该的更复杂。有人可以用初学者友好的语言来表达吗?
让我们开始个性化。
我的代码与 Bates 先生略有不同。
initializers/ominauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, "1540352575225959", "ee957abf5e851c98574cdfaebb1355f4", {:scope => 'user_about_me'}
end
routes.rb
get 'auth/:provider/callback', to: 'sessions#facebook'
sessions_controller
def facebook
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url
end
user.rb
class User < ActiveRecord::Base
acts_as_tagger
acts_as_taggable
has_many :notifications
has_many :activities
has_many :liked_comments, through: :comment_likes, class_name: 'Comment', source: :liked_comment
has_many :valuation_likes
has_many :habit_likes
has_many :goal_likes
has_many :stat_likes
has_many :comment_likes
has_many :authentications
has_many :habits, dependent: :destroy
has_many :levels
has_many :valuations, dependent: :destroy
has_many :comments
has_many :goals, dependent: :destroy
has_many :stats, dependent: :destroy
has_many :results, through: :stats
has_many :notes
accepts_nested_attributes_for :habits, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :notes, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :stats, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :results, :reject_if => :all_blank, :allow_destroy => true
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }, format: { with: /\A[a-z\sA-Z]+\z/,
message: "only allows letters" }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }, unless: -> { from_omniauth? }
has_secure_password
validates :password, length: { minimum: 6 }
def name
read_attribute(:name).try(:titleize)
end
def count_mastered
@res = habits.reduce(0) do |count, habit|
habit.current_level == 6 ? count + 1 : count
end
end
def count_challenged
@challenged_count = habits.count - @res
end
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.provider = auth.provider
user.image = auth.info.image
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.password = (0...8).map { (65 + rand(26)).chr }.join
user.email = (0...8).map { (65 + rand(26)).chr }.join+"@mailinator.com"
user.save!
end
end
def self.koala(auth)
access_token = auth['token']
facebook = Koala::Facebook::API.new(access_token)
facebook.get_object("me?fields=name,picture")
end
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Forgets a user. NOT SURE IF I REMOVE
def forget
update_attribute(:remember_digest, nil)
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
def good_results_count
results.good_count
end
# Follows a user.
def follow(other_user)
active_relationships.create(followed_id: other_user.id)
end
# Unfollows a user.
def unfollow(other_user)
active_relationships.find_by(followed_id: other_user.id).destroy
end
# Returns true if the current user is following the other user.
def following?(other_user)
following.include?(other_user)
end
private
def from_omniauth?
provider && uid
end
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase unless from_omniauth?
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
facebook.js.coffee.erb
jQuery ->
$('body').prepend('<div id="fb-root"></div>')
$.ajax
url: "#{window.location.protocol}//connect.facebook.net/en_US/all.js"
dataType: 'script'
cache: true
window.fbAsyncInit = ->
FB.init(appId: '<%= 1540372976229929 %>', cookie: true)
$('#sign_in').click (e) ->
e.preventDefault()
FB.login (response) ->
window.location = '/auth/facebook/callback' if response.authResponse
if $('#sign_out').length > 0
FB.getLoginStatus (response) ->
window.location = $('#sign_out').attr("href") if !response.authResponse
来源: omniauth-facebook。
"Being a good programmer is 3% talent and 97% not being distracted by the internet."
我在当前正在构建的应用程序中遇到了与您之前遇到的相同问题。我找到的解决方案建议使用 Koala 身份验证在其创建后立即延长初始的短期令牌。
对我来说效果很好的解决方案的改编如下...
首先,在您的 Gemfile 中包含 gem 'koala', '2.0.0'
。另外,请访问 Koala github site 了解更多信息。
现在让我们将上面的应用到user.rb的from_omniauth
方法...
def self.from_omniauth(auth)
# Sets 60 day auth token
oauth = Koala::Facebook::OAuth.new("1540352575225959", "ee957abf5e851c98574cdfaebb1355f4")
new_access_info = oauth.exchange_access_token_info auth.credentials.token
new_access_token = new_access_info["access_token"]
new_access_expires_at = DateTime.now + new_access_info["expires"].to_i.seconds
where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.provider = auth.provider
user.image = auth.info.image
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = new_access_token # auth.credentials.token <- your old token. Not needed anymore.
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.password = (0...8).map { (65 + rand(26)).chr }.join
user.email = (0...8).map { (65 + rand(26)).chr }.join+"@mailinator.com"
user.save!
end
end
附加信息:
我还注意到您正在为用户的电子邮件和密码生成一些虚拟值。我是否建议您使用 SecureRandom 库来这样做。虽然您的代码不太可能导致值冲突,但至少对于电子邮件,我建议将 (0...8).map { (65 + rand(26)).chr }.join+"@mailinator.com"
更改为更稳定的 SecureRandom.hex + "@mailinator.com"
。如果您使用电子邮件作为在应用程序中记录用户的方式,这一点尤其重要。
希望这能解决您的问题。祝你一切顺利,提姆。
更新:
很高兴我们设法回答了您的问题并一起解决了您的所有其他问题,但是这里有关于我之前谈到的重构代码的更新。 ..
考虑一种情况,通过 Facebook 登录的用户在您的网站上更改了一些关于他们自己的数据。假设他们更改了姓名、电子邮件或头像。这将正常工作 直到 同一用户尝试重新登录。将会发生的是 from_omniauth 方法将再次被触发并覆盖这些更改。这很糟糕,防止这种情况的方法是执行以下操作...
def self.from_omniauth(auth)
.
.
.
where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.provider = auth.provider
user.image = auth.info.image unless user.image != nil
user.uid = auth.uid
user.name = auth.info.name unless user.name != nil
user.oauth_token = new_access_token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.password = SecureRandom.urlsafe_base64 unless user.password != nil
user.email = SecureRandom.hex + "@mailinator.com" unless user.email != nil
user.activated = true
user.save!
end
关键是 unless user.image != nil
的使用确保只有当 :image
的初始值为 nil
时才会设置您的图像。如果说用户更改图像,则值不会是 nil
并且 from_omniauth 不会更改它。姓名、电子邮件和密码也是如此。不要将其设置为任何其他内容。
另请注意,我使用的是 SecureRandom.urlsafe_base64
而不是您的 (0...8).map { (65 + rand(26)).chr }.join
。这是因为 urlsafe_base64
生成一个随机 URL 安全的 base64 字符串,例如 i0XQ-7gglIsHGV2_BNPrdQ==
非常适合虚拟密码并且看起来也很优雅。
另一方面,我对电子邮件使用 hex
的原因是因为它会创建一个随机的十六进制字符串,例如 eb693ec8252cd630102fd0d0fb7c3485
,如果您碰巧有一些用于验证电子邮件的正则表达式(理想情况下你应该这样做)。
您可以使用 Koala::Facebook::OAuth
exchange_access_token_info
方法