47
loading...
This website collects cookies to deliver better user experience
# .env
TWITTER_API_KEY=KEY FORM TWITTER
TWITTER_API_SECRET=KEY FORM TWITTER
TWITTER_API_BEARER_TOKEN=KEY FORM TWITTER
TWITTER_API_CALLBACK=http://localhost:4200/twitter_login_oauth_callback
twitter.rb
file inside app/services/Oauth/
and copy and paste the code below to the file.# app/services/Oauth/twitter.rb
require 'net/http'
module Oauth
class Twitter
def initialize(params)
@callback = params[:callback]
@app_key = params[:app_key]
@app_secret = params[:app_secret]
end
def get_redirect
# our front end will go to the callback url
# and user will need to login from there
# e.g.
# https://api.twitter.com/oauth/authenticate?oauth_token=Bviz-wAAAAAAiEDZAAABdOLQn-s
tokens = get_request_token
oauth_token = tokens["oauth_token"]
oauth_token_secret = tokens["oauth_token_secret"]
callback_url = "https://api.twitter.com/oauth/authenticate?oauth_token=#{oauth_token}"
return {
"oauth_token": oauth_token,
"url": callback_url,
"oauth_token_secret": oauth_token_secret
}
end
def obtain_access_token(oauth_token, oauth_token_secret, oauth_verifier)
tokens = get_access_token(oauth_token, oauth_token_secret, oauth_verifier)
end
private
def get_access_token(oauth_token, oauth_token_secret, oauth_verifier)
method = 'POST'
uri = "https://api.twitter.com/oauth/access_token"
url = URI(uri)
oauth_timestamp = Time.now.getutc.to_i.to_s
oauth_nonce = generate_nonce
oauth_params = {
'oauth_consumer_key' => @app_key, # Your consumer key
'oauth_nonce' => oauth_nonce, # A random string, see below for function
'oauth_signature_method' => 'HMAC-SHA1', # How you'll be signing (see later)
'oauth_timestamp' => oauth_timestamp, # Timestamp
'oauth_version' => '1.0', # oAuth version
'oauth_verifier' => oauth_verifier,
'oauth_token' => oauth_token
}
oauth_params['oauth_callback'] = url_encode(@callback+"\n")
oauth_callback = oauth_params['oauth_callback']
base_string = signature_base_string(method, uri, oauth_params)
oauth_signature = url_encode(sign(@app_secret + '&', base_string))
authorization = "OAuth oauth_callback=\"#{oauth_callback}\", oauth_consumer_key=\"#{@app_key}\", oauth_nonce=\"#{oauth_nonce}\", oauth_signature=\"#{oauth_signature}\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"#{oauth_timestamp}\", oauth_token=\"#{oauth_token}\", oauth_verifier=\"#{oauth_verifier}\", oauth_version=\"1.0\""
# authorization = 'OAuth oauth_callback="http%3A%2F%2Flocalhost%3A9000%2Ftwitter_connection%0A", oauth_consumer_key="QJImAUogu5MUalOP2Tv5jRt3X", oauth_nonce="a9900fe68e2573b27a37f10fbad6a755", oauth_signature="Y6y8dg4ENFXorvDPu7kyjrdbVYI%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1601796648", oauth_token="NPASDwAAAAAAiEDZAAABdOzo3sU", oauth_verifier="KiPMEx5rkceLjH1sCV3LfIVsxko0sBrc%0A", oauth_version="1.0"'
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(url)
request["authorization"] = authorization
response = http.request(request)
parse_response_body(response)
end
def get_request_token
# https://wiki.openstreetmap.org/wiki/OAuth_ruby_examples
# http://www.drcoen.com/2011/12/oauth-1-0-in-ruby-without-a-gem/
# http://www.drcoen.com/2011/12/oauth-with-the-twitter-api-in-ruby-on-rails-without-a-gem/
method = 'POST'
uri = "https://api.twitter.com/oauth/request_token"
url = URI(uri)
oauth_timestamp = Time.now.getutc.to_i.to_s
oauth_nonce = generate_nonce
oauth_params = {
'oauth_consumer_key' => @app_key, # Your consumer key
'oauth_nonce' => oauth_nonce, # A random string, see below for function
'oauth_signature_method' => 'HMAC-SHA1', # How you'll be signing (see later)
'oauth_timestamp' => oauth_timestamp, # Timestamp
'oauth_version' => '1.0' # oAuth version
}
oauth_params['oauth_callback'] = url_encode(@callback+"\n")
base_string = signature_base_string(method, uri, oauth_params)
oauth_signature = url_encode(sign(@app_secret + '&', base_string))
oauth_callback = oauth_params['oauth_callback']
authorization = "OAuth oauth_callback=\"#{oauth_callback}\", oauth_consumer_key=\"#{@app_key}\", oauth_nonce=\"#{oauth_nonce}\", oauth_signature=\"#{oauth_signature}\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"#{oauth_timestamp}\", oauth_version=\"1.0\""
puts authorization
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(url)
request["authorization"] = authorization
response = http.request(request)
parse_response_body(response)
end
def url_encode(string)
CGI::escape(string)
end
def signature_base_string(method, uri, params)
encoded_params = params.sort.collect{ |k, v| url_encode("#{k}=#{v}") }.join('%26')
method + '&' + url_encode(uri) + '&' + encoded_params
end
def sign(key, base_string)
# digest = OpenSSL::Digest::Digest.new('sha1')
digest = OpenSSL::Digest::SHA1.new
hmac = OpenSSL::HMAC.digest(digest, key, base_string)
Base64.encode64(hmac).chomp.gsub(/\n/, '')
end
def generate_nonce(size=7)
Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/, '')
end
def parse_response_body(response)
ret = {}
body = response.read_body
body.split('&').each do |pair|
key_and_val = pair.split('=')
ret[key_and_val[0]] = key_and_val[1]
end
ret
end
end
end
oauth_sessions_controller
's get_login_link
method, which then redirect the page.oauth_sessions_controller.rb
, and put it under your controllers/api/v1
directory# controllers/api/v1/oauth_sessions_controller.rb
module Api::V1
class OauthSessionsController < ApplicationController
# GET /v1/twitter_sign_in_link
def get_login_link
redirect_to OauthSession.get_sign_in_redirect_link
end
end
end
config/routes.rb
file to setup the /v1/twitter_sign_in_link endpoint
.# config/routes.rb
scope module: 'api' do
namespace :v1 do
...
get 'twitter_sign_in_link' => 'oauth_sessions#get_login_link'
...
end
end
OauthSession.get_sign_in_redirect_link
? This is going to return us a redirect URL to the Twitter consent screen.get_sign_in_redirect_link
, we will first generate the OauthSession
model.rails g model OauthSession
OauthSession
model, which stores information such as oauth_token
, and oauth_token_secret
, and a corresponding migration fileYYYYMMDDTTTT_create_oauth_sessions.rb
class CreateOauthSessions < ActiveRecord::Migration[6.1]
def change
create_table :oauth_sessions do |t|
t.string :provider
t.string :oauth_token
t.string :oauth_token_secret
t.timestamps
end
end
end
rails db:migrate
.get_sign_in_redirect_link
class method to OauthSession.# models/oauth_session.rb
class OauthSession < ApplicationRecord
def self.get_sign_in_redirect_link
params = {callback: ENV['TWITTER_API_CALLBACK'], app_key: ENV['TWITTER_API_KEY'], app_secret: ENV['TWITTER_API_SECRET']}
twitter_service = Oauth::Twitter.new(params)
result = twitter_service.get_redirect // {oauth_token, oauth_token_secret, url}
oauth_token = result[:oauth_token]
oauth_token_secret = result[:oauth_token_secret]
# Save the token and secret to the table, so that we can use it later
twitter_auth_session = OauthSession.where("oauth_token = ? and provider = ?", oauth_token, "Twitter").first
unless twitter_auth_session
twitter_auth_session = OauthSession.new()
end
twitter_auth_session.oauth_token = oauth_token
twitter_auth_session.oauth_token_secret = oauth_token_secret
twitter_auth_session.provider = "Twitter"
twitter_auth_session.save
return result[:url]
end
end
callback_uri
with oauth_token
and oauth_verifier
.callback_uri
:http://localhost:4200/twitter_login_oauth_callback?oauth_token=NPcudxy0yU5T3tBzho7iCotZ3cnetKwcTIRlX0iwRl0&oauth_verifier=uw7NjWHT6OJ1MpJOXsHfNxoAhPKpgI8BlYDhxEjIBY
/v1/twitter_sign_in
) to pass oauth_token
and oauth_verifier
back to our backend server.config/routes.rb
file to setup the /v1/twitter_sign_in endpoint
.# config/routes.rb
scope module: 'api' do
namespace :v1 do
...
post 'twitter_sign_in' => 'twitter_auths#sign_in'
...
end
end
twitter_oauths_controller.rb
and save to /controllers/api/v1/
# twitter_oauths_controller.rb
module Api::V1
class TwitterOauthsController < ApplicationController
# POST /v1/twitter_sign_in
def sign_in
# converting the request token to access token
oauth_token = params[:oauth_token]
oauth_verifier = params[:oauth_verifier]
if oauth_token && oauth_verifier
user = TwitterAuth.sign_in(oauth_token, oauth_verifier)
if user
header = user.create_new_auth_token
response.set_header('access-token', header["access-token"])
response.set_header('token-type', header["token-type"])
response.set_header('client', header["client"])
response.set_header('expiry', header["expiry"])
response.set_header('uid', header["uid"])
render json: {data: user}, status: :ok
else
res = {
"success": false,
"errors": [
"Invalid login credentials. Please try again."
]
}
render json: res, status: :unauthorized
end
else
res = {
"success": false,
"errors": [
"Failed to sign in with Twitter"
]
}
render json: res, status: :unauthorized
end
end
end
end
oauth_token
and oauth_verifier
are available, then we will try to use them to sign in to our application.
user
is available, then use create_new_auth_token
method from devise_token_auth
to generate the header metaTwitterAuth.sign_in(oauth_token, oauth_verifier)
, we will first generate the TwitterAuth
model.rails g model TwitterAuth
YYYYMMDDTTTT_create_twitter_auths.rb
class CreateTwitterAuths < ActiveRecord::Migration[6.1]
def change
create_table :twitter_auths do |t|
t.string :twitter_user_id
t.string :screen_name
t.references :user, foreign_key: true
t.timestamps
end
end
end
user.rb
, add has_one :twitter_auth
twitter_auth.rb
, add belongs_to :user
rails db:migrate
.sign_in
class method to TwitterAuth.# /models/twitter_auth.rb
class TwitterAuth < ApplicationRecord
belongs_to :user
def self.sign_in(oauth_token, oauth_verifier)
params = {callback: ENV['TWITTER_API_CALLBACK'], app_key: ENV['TWITTER_API_KEY'], app_secret: ENV['TWITTER_API_SECRET']}
# 1. look up the oauth_token_secret by oauth_token
twitter_auth_session = OauthSession.where("oauth_token = ? and provider = ?", oauth_token, "Twitter").first
if twitter_auth_session
twitter_service = Oauth::Twitter.new(params)
oauth_token_secret = twitter_auth_session.oauth_token_secret
twitter_resp = twitter_service.obtain_access_token(oauth_token, oauth_token_secret, oauth_verifier)
twitter_user_id = twitter_resp["user_id"]
screen_name = twitter_resp["screen_name"]
# Here is the fun part
# 1. we look up if the twitter_user_id exist
# if yes, then it's an existing user
# if no, then it's a new user
# for new user we will create a new account
# for existing user, we will look up the user info, and return the header for auth
twitter_auth = TwitterAuth.find_by_twitter_user_id(twitter_user_id)
if twitter_auth
# return the user basic on the twitter id
user = twitter_auth.user
else
# insert a new twitter_auth and also create a new user account
str = (0...8).map { (65 + rand(26)).chr }.join
password = Digest::SHA256.base64digest "#{twitter_user_id}#{screen_name}#{str}".first(8) # generate a password
user = User.create(email: "#{screen_name}@yourwebsite.com", password: password)
twitter_auth = TwitterAuth.new()
twitter_auth.user_id = user.id
twitter_auth.twitter_user_id = twitter_user_id
twitter_auth.screen_name = screen_name
twitter_auth.save
end
twitter_auth_session.delete # remove the auth session
return user
else
return nil
end
end
end
omniauth-twitter
. This is more a "hack" than a solution, it might be easier and better to use the omniauth-twitter
gem. I hope you have enjoyed this tutorial, There is much more to be done, but this should get you started. Thanks for reading!