omniauth-facebook + rails 3 + authlogic debug

Personally I prefer Devise to Authlogic but recently I have to work with Authlogic on a legacy codebase. But I still like Devise because it’s up-to-date, clean, easier to use…

omniauth-facebook provides a cool way to login with FB – display a popup instead of redirecting users to FB login page then back to our site. Obviously this is the way we want :)

We won’t re-invent the wheel here, let’s follow Railcasts #360 to have omniauth-facebook integrated to your app. These are some issues you may encounter:

1.  Can’t log users in manually with Authlogic:

In some cases the user can’t be logged in. For me, the reasons were:

  • validations failed (lack/duplicated of email/username…): some FB users set their emails to private so ommiauth-facebook can’t get their emails, you have to set a fake email in order to pass the email validation, therefore the new user can be created in database and gets logged in.NOTES: the first time users login with their FB accounts, their accounts will be saved to database and they will be logged in automatically, we just have to manually log FB users in only when the FB accounts are already created in our database.
    user.email = auth.info.email || "fb_user_#{Time.now.hash.abs}@email.com"
  • password not present: Devise provides a method Devise.friendly_token ( detailed guide here ) to manually create a password  but Authlogic doesn’t. You have to provide password and password_confirmation manually, make sure they’re the same.
  • create a new UserSession in controller: to log user in manually, we do
    UserSession.create(my_user_object, true)

    I’m not sure why but user won’t get logged in if the above is called in controller, moved it to model works.

2.  Invalid credentials

you see something like this in the log console:

(facebook) Authentication failure! invalid_credentials: OmniAuth::Strategies::OAuth2::CallbackError, OmniAuth::Strategies::OAuth2::CallbackError

If you remove the JS code you won’t get this anymore, but the popup will be gone as well. It seems this is a bug of omniauth-facebook 1.4.1 (the newest version at the time of this writing), let’s use omniauth-facebook 1.4.0 instead, everything works just fine then.

3.  FB errors

You’ll may get some error messages while trying to login with FB, make sure in your FB app configuration, Sandbox Mode is set to Disabled.

Rails + oauth-plugin + mongodb part 2: Consumer

In previous post, we went through the process to create a Provider with Oauth-plugin that works with Mongodb using mongid as the driver. Here we will build the Consumer using mongoid too.

This Consumer app will run on port 4000 (rails s -p 4000) and connect with the Provider app which will be running on port 3000.

Step 1 – step 3

Exactly the same as in previous post for Provider

4. Generates things

rails g devise:install
rails g devise User
rails g controller Welcome index
rails g oath_consumer User
rm public/index.html

Add this to your User model:

references_many :consumer_tokens
index "consumer_tokens.token"

In app/model/consumer_token.rb, find the line reads

embedded_in :user, :inverse_of => :consumer_tokens

change it to

referenced_in :user, :inverse_of => :consumer_tokens

In oauth_consumers_controller.rb, comment out the line reads

before_filter :login_required, :only=>:index

Uncomment/add new line that reads

before_filter :authenticate_user!, :only=>:index

In oauth_consumers_controller.rb, make sure these methods are NOT commented out:

go_back, logged_in?, current_user=, deny_access!

Added this to User model:

references_one :test, :class_name => "TestToken", :dependent => :destroy

(TestToken is the model for the provider, we would have TwitterToken, FacebookToken…etc)

Create a model file named test_token.rb in app/models/ with the content:

class TestToken < ConsumerToken
  TEST_SETTINGS = {
    :site => 'http://localhost:3000', # this is the URL to provider app
    :request_token_path => '/oauth/request_token',
    :access_token_path => '/oauth/access_token',
    :authorize_path => '/oauth/authorize'
  }

  def self.consumer(options={})
    @consumer ||= OAuth::Consumer.new(credentials[:key], credentials[:secret], TEST_SETTINGS.merge(options))
  end
end

In config/routes.rb, add your root path root :to => "welcome#index"

5. Communicate with the Provider

Let’s start provider app on port 3000 and consumer app running on port 4000 (rails s -p 4000)

Navigate to http://localhost:3000/users/sign_up to register an account.

Navigate to http://localhost:3000/oauth_clients/ to register your app with these info:

Name: Test consumer
Main Application URL: http://localhost:4000/
Callback URL: http://localhost:4000/oauth_consumers/test/callback

You will be redirected to oauth_client show page with credentials (yours will be different)

Consumer Key: d8KBiaD98Mnp2vyB9A8ZSAT0vpKu5kdFtAXUsZup
Consumer Secret: UDdD5HAefrRZ1afguDy0WrTALYwZ8KXWKgLiSJCE
Request Token URL http://localhost:3000/oauth/request_token
Access Token URL http://localhost:3000/oauth/access_token
Authorize URL http://localhost:3000/oauth/authorize

In config/initializers/oauth_consumers.rb, add the credentials above. The content will look like:

OAUTH_CREDENTIALS = {
  :test => {
    :key => 'd8KBiaD98Mnp2vyB9A8ZSAT0vpKu5kdFtAXUsZup',
    :secret => 'UDdD5HAefrRZ1afguDy0WrTALYwZ8KXWKgLiSJCE',
    :expose => true
  }
}

Restart your Consumer app if it was running when you changed the content of this initializer.

Modify the content of Welcome#index to get the provider data:

class WelcomeController < ApplicationController
  def index
    @consumer_tokens = TestToken.where(:user_id => current_user.id)
    @token = @consumer_tokens.first.client
    logger.info 'private data' + @token.get('/data/index').body
  end
end

Go to http://localhost:4000/oauth_consumers to see all the services. Actually we have only 1 here, it’s the ‘test’ service which owned by ‘TestToken’ model.

Click on the service (here is test) then give it access

Go to http://localhost:4000 and you will see the data from provider printed out in log console

You can find the source code for Consumer here

Rails + oauth-plugin + mongodb part 1: Provider

Recently I had a chance to work with Oauth and Rails using oauth-plugin and mongodb. Although oauth-plugin itself has a documentation but actually doesn’t cover the mongoid part detailed enough. Also I found this very good tutorial but it’s for SQLite3 only: Oauth Provider, Oauth Consumer

Base on the above posts, to get Rails and oauth-plugin working with mongodb, you need to do some changes.

Firstly, create a oauth-sample directory, you’ll put provider and consumer apps in there.

1. Create new project

    • mkdir oauth
    • cd oauth
    • rails new provider
    • cd provider
    • add these gems to your Gemfile
gem 'rails', '3.0.12'
gem 'devise'
gem 'oauth-plugin', '~> 0.4.0'
gem 'mongoid' gem 'bson_ext'
  • bundle install`

Now you have:

  • an authentication system installed (Devise)
  • oauth-plugin which helps you a lot in generating files for Oauth
  • mongoid a driver for mongodb
  • bson_ext which mainly for mongo peformance improvement

2. Config the Rails app to use mongodb

  • delete config/database.yml
  • rails g mongoid:config
  • replace require 'rails/all' in config/application.rb with
require "actioncontroller/railtie"
require "actionmailer/railtie"
require "activeresource/railtie"
require "rails/testunit/railtie"
#require "sprockets/railtie" # Uncomment this line for Rails 3.1+

3. Config the generators

This step is important, you need to do this before generating/installing Devise model, Oauth things…

Add this code into your application.rb, uncomment haml/rspec if you use them. Keep the mongoid option as that is what we want.

config.generators do |g|
  g.orm :mongoid
  #g.template_engine :haml
  #g.test_framework :rspec
end

From now on, the generators know they should invoke mongoid instead of activerecord when generating things…

4. Generates things

rails g devise:install
rails g devise User
rails g controller Data index
rails g oath_provider
rm public/index.html

Add this into your User model:

references_many :client_applications
references_many :tokens, :class_name => "OauthToken", :order => "authorized_at desc"

Add this into your oauth_clients_controller.rb:

alias :login_required :authenticate_user!

In oauth_clients_controller.rb, find and replace the line reads

@tokens = current_user.tokens.find(:all, :conditions => 'oauth_tokens.invalidated_at is null and oauth_tokens.authorized_at is not null')

with

@tokens = current_user.tokens.includes(:client_application).where('oauth_tokens.invalidated_at is null and oauth_tokens.authorized_at is not null')

In config/application.rb, add this code:

require 'oauth/rack/oauth_filter'
config.middleware.use OAuth::Rack::OAuthFilter

In app/controllers/oauth_controller.rb, add this code:

alias :logged_in? :user_signed_in?
alias :login_required :authenticate_user!

Modify the Data#index to add some data:

class DataController < ApplicationController
  before_filter :oauth_required

  def index
    @data = { "coincoin" => "o< o<" }
    respond_to do |format| 
      format.json { render :json => @data } 
    end
  end
end

In config/routes.rb, add your root path root :to => "data#index"

5. Explain :include issue

As mentioned in oauth-plugin guide we should use

has_many :tokens, :class_name => "OauthToken", :order => "authorized_at desc", :include => [:client_application] with the :include

Actually :include is for ActiveRecord eager-loading feature and it doesn’t work with Mongoid, so I thought I should find an equivalent of it in Mongoid.

After doing a research I found that Mongoid originally didn’t support eager-loading, then started supporting eager-loading from version 2.2.0

We can use this feature by using Model#includes method as guided here

So I think we should remove the :include => [:client_application] option in User model, then add .includes(:client_application) to the controller as above.

6. Issues with oauth-plugin + mongoid

You can make these below changes yourself or simply get from here. I folked from original repo and modified.

You will encounter these one when you trying to connect consumer with provider on step 5 in consumer guide.
- Dynamic finders don’t seem to work. You’ll get the exception with ‘findbykey’, we need to modify the oauth-plugin gem directly (as the author hasn’t provide a fix yet).
Do this commands:

bundle show oauth-plugin # to know where it was installed.
cd /path/to/oauth-plugin/installed
cd lib
grep -rn 'find_by_' .

Let’s change all the .find_by_<a_key>(<a_value>) with .where(:<a_key> => <a_value>).first

E.g:

#this one
@token = ::RequestToken.findbytoken! params[:oauth_token]
#should be changed to
@token = ::RequestToken.where(:token => params[:oauth_token]).first

- undefined method 'expand_complex_criteria' for #<Array:0x00000102934308>

In lib/oauth/rack/oauth_filter.rb of oauth-plugin gem, find the line (around line #27) which reads:

if token = Oauth2Token.first(:conditions => ['invalidated_at IS NULL AND authorized_at IS NOT NULL and token = ?', token_string])

change it to

if token = Oauth2Token.where(:invalidated_at => nil, :authorized_at.ne => nil, :token => token_string).first

Then find the line (around #45) which reads:

oauth_token = client_application.tokens.first(:conditions => { :token => request_proxy.token })

change it to

oauth_token = client_application.tokens.where(:token => request_proxy.token).first

And we’re done for Provider

You can get the source code for Provider here

BrowserCMS from development to production

There’re some Rails CMS such as Radiant, Refinery, BrowserCMS… and I find BrowserCMS the best one to build simple websites for my customers, we can easily create dynamic content for you websites with Portlet, create new object to manage by with Content type, and easily create template for each page (homepage, subpages…).

In this post we won’t talk about how to use BrowserCMS, please go to the BrowserCMS Wiki if you’re finding that information. This post just concentrates on things you need to do to get BrowserCMS work properly on server in production mode.

For me, I use a VPS with Rails 3, Ruby 1.9.2-p180 (with RVM installed), Rubygem 1.6.2, Nginx + Passenger.

I have it work well on my development machine, but on VPS under production mode, there are 2 problems:

  1. all stylesheets are NOT applied
  2. images can be uploaded but won’t show up both in back-end and front-end

the first one will be resolved if you read the Deployment Guide of BrowserCMS, you’ll some basic settings need for production mode and set the config.serve_static_assets to true:

# To send CMS files, the new Rails 3 default (using X-Sendfile) must be 
#commented out, or Apache mod_sendfile should be installed. 
# See http://codequest.eu/articles/rails3-apache-passenger-and-empty-file-using-send-file for details 
#
# config.action_dispatch.x_sendfile_header = "X-Sendfile"

# This has to be enabled so that core CMS assets (like internal buttons, css..)
# can be served from the bcms gem.
config.serve_static_assets = true

# This should match the URL for your public CMS site.
SITE_DOMAIN="www.sitedomain.com"

# You may need to edit your mail server address as well in the next line.
config.action_mailer.smtp_settings = {:address => 'mail.yourmailserver.com', 
                                      :domain => "#{SITE_DOMAIN}"}

The second one it’s because we’re using Nginx, we need to uncomment this line in config/initializers/browsercms.rb

# For nginx:
config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

then images issue gone.

Really easy but it took me 2 hours to figure out, I had tried to check the chmod of uploaded files, check the location of uploaded files, check the database....

Hope this helps

Mac OS Lock screen with keyboard shortcut

When I step away from my Mac at work, I want a quick way to lock the screen, and hitting a hot-corner with the mouse is problematic for me. This hint details how to lock the screen from the keyboard by using Automator to build a Service in Snow Leopard.

First, check the General tab on the Security System Preferences panel to ensure that the Require password [some period] after sleep or screen saver begins box is checked.

Then, open Automator in the Applications folder, and select Service from the screen that appears. At the top of the new Service’s actions, in the Service receives drop-down, select no input from the options. Make sure that any application is selected in the second drop-down.

Add the Start Screensaver action (in the Utilities group of actions) to the Service by dragging it to the right. Save the Service (Automator does not ask you where to save it, just to name it). Next, open System Preferences and select the Keyboard preference pane. Select the Keyboard Shortcuts tab at the top, then the Services group on the left. The service you created should be near the bottom of the list of Services under the General disclosure triangle.

Double-click on the right side of the entry for the Service you created and assign a keyboard shortcut. This was a bit unintuitive, because the shortcut column is not distinctly visible, so it is not obvious that you can double-click in the assigned shortcut column to add a shortcut.

I had difficulty picking a keyboard shortcut that would work in 10.6.0. Command-L did not work for me, because it is assigned the Show All Preferences menu item in System Preferences. Control-L also did not work for me. Command-Shift-L did work once I reassigned the Search with Google Service a different shortcut.

Exit the keyboard preference pane to give it a try.