🆔
Twitter auth in Rails (2/2)
on programminglast updated 2/7/21
In part 1, we finished up by tweaking settings in the Twitter developer portal. Our homepage link and Twitter-hosted login don't work quite yet; we need to implement OmniAuth first.
Configuring OmniAuth
OmniAuth is an authentication system that serves as middleware.
It sits between our application and external providers, enabling users to log into our application with an existing account on Twitter, Google, or other popular platforms.
The omniauth
& omniauth-twitter
gems are required for this, and I recommend adding the twitter
gem (more on this here) to simplify interactions with the Twitter API after logging in.
# liked/Gemfile
gem 'omniauth'
gem 'omniauth-twitter'
gem 'twitter'
...
So that our homepage link actually redirects to a Twitter login page, we'll add an omniauth.rb
file in config/initializers
with the following block:
# liked/config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET']
end
# liked/app/views/users/show.erb
<%= button_to "/auth/twitter", method: :get %>
Upon attempted logins, Twitter's servers respond with information for the user's Twitter account via a GET request to the callback URL we set in our developer portal (at the end of part 1).
We'll add two callback routes to handle responses for both successful and unsuccessful attempts.
# liked/config/routes.rb
Rails.application.routes.draw do
root 'users#home'
get '/auth/failure' => '/'
get '/auth/twitter/callback' => 'sessions#create'
end
Unsuccessful logins will hit that 2nd route and direct users back to our root route: the homepage.
The 3rd route will handle responses for successful logins. GET requests to the callback URL auth/twitter/callback
will be matched to the sessions
controller's create
action.
For context, take a look at an example response from the omniauth-twitter
documentation.
Handling the Twitter API response
We can process responses in the sessions#create
controller action or as I've done below, move that logic into a model as a dedicated method .
The sessions#create
action first invokes a User
class method, passing it the response. To assist in parsing this response hash, I also added a simple helper method.
# liked/app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
# GET /auth/twitter/callback
def create
user = User.from_omniauth(auth)
end
private
# helper method to assist in parsing omniauth responses
def auth
request.env['omniauth.auth']
end
end
In this User
class method, we need to find the existing account associated with this Twitter account or create a new account altogether. We'll do so with the value for the uid
property, a unique identifier for every Twitter account, in the response.
#find_or_create_by
is a handy ActiveRecord method for this sort of scenario. When a record is found with the given uid
attribute, the block is skipped and that record is returned.
When a record is not found — meaning the authenticating user has not logged into our application with that Twitter account before — the block is invoked to create and return a new user record.
# liked/app/models/user.rb
class User < ApplicationRecord
validates :name, presence: true
validates :handle, presence: true, uniqueness: true
validates :token, presence: true, uniqueness: true
validates :secret, presence: true, uniqueness: true
# finds existing user or creates new user
def self.from_omniauth(auth)
self.find_or_create_by(uid: auth['uid']) do |u|
u.name = auth[:info][:name]
u.handle = auth[:info][:nickname]
u.token = auth[:credentials][:token]
u.secret = auth[:credentials][:secret]
end
end
end
The access token
and secret
can be used to make requests to the Twitter API retrieving the user's follows, likes, and so on. Like uid
, they're unique to each Twitter account and do not expire.
So .from_omniauth
returns a user record for the current user. We'll then store attributes for that record in the session hash (more on this here) so the user doesn't have to log in again.
# liked/app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
# GET /auth/twitter/callback
def create
user = User.from_omniauth(auth)
session[:user_id] = user.id
session[:token] = user.token
session[:secret] = user.secret
redirect_to user_path(user)
end
private
# helper method to simplify parsing omniauth response
def auth
request.env['omniauth.auth']
end
end
Where to redirect the user after this is completely up to you. At a minimum, you'll probably need another route, controller action, and view.
Conclusion
If you've read this far, I have a pair of final recommendations:
- add a logout link and corresponding route / controller action
- use helper methods to verify that users have logged in or out
I suggest taking care of that first piece immediately after adding the login button, route, and controller action. Here's an example:
# liked/config/routes.rb
Rails.application.routes.draw do
root 'users#home'
get '/auth/failure' => '/'
get '/auth/twitter/callback' => 'sessions#create'
post '/logout' => 'sessions#destroy' # logout route
resources :users, only: [:show]
end
# liked/app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
# GET /auth/twitter/callback
def create
user = User.from_omniauth(auth)
session[:user_id] = user.id
session[:token] = user.token
session[:secret] = user.secret
redirect_to user_path(user)
end
# POST /logout - logout controller action
# clears the session, 'logging out' the user and redirecting them
def destroy
session.clear
redirect_to root_path
end
private
def auth
request.env['omniauth.auth']
end
end
# liked/app/views/layouts/application.html.erb - layout with logout link
...
<%= link_to "Sign out", logout_path, method: :post %>
...
As for those helper methods, here are some examples. They can be used to simplify controller logic, such as filters for controller actions.
I'm happy to try and answer any questions you might have related to implementing an OmniAuth strategy for Twitter in your Rails app (either here or in part 1). Please just email me.
I've also included some links below that helped me with this process.
INSPIRATION
- #241 Simple Omniauth, a video tutorial by Ryan Bates
- sign-in-with twitter, an example application by Erik Berlin
- omniauth-twitter, a Ruby gem for implementing OmniAuth with Twitter
- twitter, a Ruby gem for interacting with the Twitter API