October 27, 2010
#19 Implementing Authentication - OmniAuth
With the troubleshooting done, we can now write the code for our user authentication interface.
The Routes
In the last post, we saw that we'll need to handle two routes for our authentication scheme: one for success and one for failure.
config/routes.rb
Arailsdemo::Application.routes.draw do match '/auth/:provider/callback', :to => 'sessions#create' match '/auth/failure', :to => 'sessions#failure' ... end
OmniAuth Configuration
config/initializers/omniauth.rb
require 'omniauth/openid' require 'openid/store/memcache' Rails.application.config.middleware.use OmniAuth::Builder do provider :github, "githubkey", "githubsecret" provider :facebook, "facebookkey", "facebooksecret" provider :twitter, 'twitterkey', "twittersecret" provider :open_id, OpenID::Store::Memcache.new(Dalli::Client.new) end ...
The Login Page
We use some 'AuthButtons' to make some fancy links to the OAuth and OpendID providers.
app/views/sessions/new.html.haml
- title "Login" .loginLinks = link_to image_tag('github_logo.png', :size => "74x53", :alt => "Github"), '/auth/github', :title => 'Github' = link_to image_tag('google_64.png', :size => "64x64", :alt => "google"), '/auth/open_id?openid_url=google.com/accounts/o8/id', :title => 'Google' = link_to image_tag('facebook_64.png', :size => "64x64", :alt => "Facebook"), '/auth/facebook', :title => 'Facebook' = link_to image_tag('twitter_64.png', :size => "64x64", :alt => "Twitter"), '/auth/twitter', :title => 'Twitter' = link_to image_tag('yahoo_64.png', :size => "64x64", :alt => "Yahoo"), '/auth/open_id?openid_url=yahoo.com', :title => 'Yahoo' = link_to image_tag('openid_64.png', :size => "64x64", :alt => "OpenID"), '/auth/open_id', :title => 'OpenID' %hr %p You can login to this site ... %p Learn about = link_to "OpenID", "http://openid.net/" or = link_to "OAuth", "http://oath.net"
The Sessions Controller
Our create action will handle the success response from the OpenID and OAuth providers. The information from these providers will be available in the controller's request.env['omniauth.auth'] variable. We store the name or email from this hash and redirect to the posts index page.
Failed authentications are handled through a separate method and simply redirected to the login page with a flash message.
Finally, we update our 'user_signed_in?' method and add a 'logged_out?' method.
Failed authentications are handled through a separate method and simply redirected to the login page with a flash message.
Finally, we update our 'user_signed_in?' method and add a 'logged_out?' method.
app/controllers/sessions_controller.rb and app/controllers/application_controller.rb
class SessionsController < ApplicationController def new end def create reset_session # see http://guides.rubyonrails.org/security.html#session-fixation info = request.env["omniauth.auth"] session[:name] = info["user_info"]["name"] || info["user_info"]["email"] || info["user_info"]["nickname"] || "fellow Ruby on Rails enthusiast" redirect_to posts_url, :notice => "Welcome #{session[:name]}!" end def failure redirect_to login_url, :alert => 'Sorry, there was something wrong with your login attempt. Please try again.' end def destroy reset_session flash[:notice] = "Logged out." redirect_to posts_url end end ________________________________________ # application_controller.rb class ApplicationController < ActionController::Base helper_method :admin?, :user_signed_in?, :logged_out? ... def user_signed_in? !session[:name].blank? end def logged_out? !user_signed_in? && !admin? end end
Updating the Nav Bar
We toggle the login and logout links based on the user's status and show the user's name if he/she is logged in. However, since we're caching the home page, we need to exclude the name from that page.
app/shared/nav_bar.html.haml
#navBar %ol %li= link_to 'Home', root_url %li= link_to 'Building This Site', posts_url - if admin? ... %li.logout= link_to "Logout", logout_path - elsif user_signed_in? - if controller.class != PagesController %li= link_to "Logout (#{session[:name]})", logout_url - else %li= link_to "Logout", logout_url - elsif logged_out? %li= link_to 'Login', login_url
Sessions Store
Instead of using the default CookieStore for storing our session information, we'll switch to keeping that information on the server side in our database. After configuring Rails to use the ActiveRecordStore, we run the rake take to generate the migration file.
config/initializers/session_store.rb and Terminal
# session_store.rb # Arailsdemo::Application.config.session_store :cookie_store, :key => '_arailsdemo_session' Arailsdemo::Application.config.session_store :active_record_store ________________________________________ # Terminal > rake db:sessions:create > rake db:migrate
Clearing Out the SessionStore Periodically
After reading the Rails security guide, we setup a means to remove old sessions. (Plus, we don't want the sessions table getting too big.) We use the code in the security guide to create a method that will delete the sessions that are more than one hour old. Then we create a rake task that will call that method. Lastly, we add the Heroku cron add-on. With this, every day our sessions table will be cleared, and effectively, a session will last up to 25 hours.
app/modesl/session.rb, lib/tasks/cron.rake and Terminal
# session.rb class Session < ActiveRecord::Base def self.sweep(time = 1.hour) time = time.split.inject { |count, unit| count.to_i.send(unit) } if time.is_a?(String) delete_all "created_at < '#{time.ago.to_s(:db)}'" end end ________________________________________ # cron.rake desc "This task is called by the Heroku cron add-on" task :cron => :environment do Session.sweep end ________________________________________ # Terminal > heroku addons:add cron:daily
(Please login to submit comments.)
Comments