Thursday, September 30, 2010

Ruby, Rails, OpenID, and Google Integration for the Busy Developer

We're busy developers, you and I, and we want to get back to building our awesome HTML5 app and not muck around too much with user sign in and registration.  There are enough accounts out there, there's no reason why your users need to create a new account identity just for your system.  Let them sign in with an existing account, your users will thank you!!

We're going to learn the bare minimum required to allow your users to use their Google Account to register and sign in to your Ruby on Rails 3 web app.  I think you'll find that it's very easy to add OpenID support for Google Accounts, especially for Rails 3 web apps.

Assumptions:
  • You are building a Ruby on Rails 3 web app.
  • You do not yet have user authentication or registration for your app.  A future article will show you how to add Google OpenID to your authlogic app, but this article is all about starting from scratch.
  • You want your users to use their Google Accounts for their identity.
  • You have a model named User which represents a registered user in your system.
(Once you have Google Account authentication via OpenID, you are more than ready to publish your web app in the Chrome Web Store.  I've written a quick start guide for the Chrome Web Store which will help you get your app into the store in under 30 minutes.)

Step Zero: Add attributes to User
Although we are assuming you already have a User model, we need to add a identifier_url attribute.  This attribute, which should be unique, stores the OpenID URL identifying the user.

You can use a migration like:



class AddIdentifierUrlToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :identifier_url, :string
    add_index :users, :identifier_url, :unique => true
  end

  def self.down
    remove_index :users, :identifier_url
    remove_column :users, :identifier_url
  end
end


This code also assumes you have email, first_name, and last_name as attributes for your user, in addition to identifier_url.

Step One: Add the gems
You'll need both ruby-openid and rack-openid added to your Gemfile.  It's as simple as:



gem "ruby-openid"
gem "rack-openid"


And then, of course, run bundle install which will pull down the gems and lock them into your project.

Step Two: Tell Rails about Rack::OpenID

Open up config/application.rb and add


require 'rack/openid'


to the file of the file.  Then, inside the Application you'll need to add


config.middleware.use 'Rack::OpenID'

Step Three: Create AuthenticationHelper
Create an authentication_helper.rb in the app/helpers directory.  This file should include:



module AuthenticationHelper
  def signed_in?
    !session[:user_id].nil?
  end
  
  def current_user
    @current_user ||= User.find(session[:user_id])
  end
  
  def ensure_signed_in
    unless signed_in?
      session[:redirect_to] = request.request_uri
      redirect_to(new_session_path)
    end
  end
end


Step Four: Tell your app about AuthenticationHelper
Open up app/controllers/application_controller.rb and add the line include AuthenticationHelper



class ApplicationController < ActionController::Base
  protect_from_forgery
  include AuthenticationHelper
end


Step Five: Add the routes
Open up config/routes.rb and add the line:



resource :session


Step Six: Create the SessionsController
Create app/controller/sessions_controller.rb which should look like:



class SessionsController < ApplicationController
  skip_before_filter :verify_authenticity_token
  
  def new
    response.headers['WWW-Authenticate'] = Rack::OpenID.build_header(
        :identifier => "https://www.google.com/accounts/o8/id",
        :required => ["http://axschema.org/contact/email",
                      "http://axschema.org/namePerson/first",
                      "http://axschema.org/namePerson/last"],
        :return_to => session_url,
        :method => 'POST')
    head 401
  end
  
  def create
    if openid = request.env[Rack::OpenID::RESPONSE]
      case openid.status
      when :success
        ax = OpenID::AX::FetchResponse.from_success_response(openid)
        user = User.where(:identifier_url => openid.display_identifier).first
        user ||= User.create!(:identifier_url => openid.display_identifier,
                              :email => ax.get_single('http://axschema.org/contact/email'),
                              :first_name => ax.get_single('http://axschema.org/namePerson/first'),
                              :last_name => ax.get_single('http://axschema.org/namePerson/last'))
        session[:user_id] = user.id
        if user.first_name.blank?
          redirect_to(user_additional_info_path(user))
        else
          redirect_to(session[:redirect_to] || root_path)
        end
      when :failure
        render :action => 'problem'
      end
    else
      redirect_to new_session_path
    end
  end
  
  def destroy
    session[:user_id] = nil
    redirect_to root_path
  end
end


Note that the create method handles both sign in and registration.

It's also very important to point out that even though this code asks for attributes like firstName and lastName, the OpenID provider might not return them to your app.  So, you'll probably want to add a second step for new users to collect more information.  This is accomplished with the user_additional_info_path(user).

Step Seven: Create an error page
Just in case it didn't work, you can display a helpful message for your user.  Create a app/views/sessions/problem.html.erb



<h1>DOH!</h1>

<p>Looks like your Google login didn't quite work.  <%= link_to 'Try again?', new_session_path %></p>


Step Eight: Protect your pages
To ensure that only signed in users can access your resources, simply place before_filter :ensure_signed_in in any controller you wish to protect.

For example:



class AdditionalInfosController < ApplicationController
  before_filter :ensure_signed_in
  
  def show
    @user = User.find(params[:user_id])
  end
  
  def update
    @user = User.find(params[:user_id])
    @user.update_attributes(params[:user])
    redirect_to(session[:redirect_to] || root_path)
  end
  
end


Summary
Congrats, you've gone from zero user registration and authentication to a full OpenID based Google Account based system.  All of this code can be found in the Bracket Baby app on Github.

Wednesday, September 29, 2010

The Busy Developer's Guide to Publishing Your App Into the Chrome Web Store

You're busy, I'm busy, let's get right to it.  I'm writing this blog post for a buddy of mine who has an awesome app that would do great in the Chrome Web Store.  He's crazy busy, so he needs the "just tell me what to do" guide, so here we are.

Wait, what, you haven't heard of the Chrome Web Store?  Are you a web developer that writes awesome web apps and wishes there was a great consumer marketplace to distribute and sell your apps?  Yup, that's the Chrome Web Store.  That's what I thought, you want in on this.

We're going to assume you already have your app.  If not, go build an amazing HTML5 app.  I'll wait.

Got your app?  Good, let's go.
  1. Create a Google Account.  You might even have one, and you don't need a GMail account.  This Google Account will be used to publish your app, so you might want to create a role account, not tied to a specific person.
  2. Create a directory and name it something like my-app-rocks.
  3. Write a manifest file describing your app.  In fact, use the template below, and swap out the details.  You can do a bit more with the manifest, like request extended permissions, but this is about getting your app into the store ASAP, so copy and paste here is your friend.


    {
      "name": "YOUR AWESOME APP NAME",
      "description": "IT MAKES DOUBLE RAINBOWS",
      "version": "1",
      "app": {
        "urls": [
          "*://YOUR.APP.COM/URL"
        ],
        "launch": {
          "web_url": "http://YOUR.APP.COM/START/PAGE"
        }
      },
      "icons": {
        "128": "icon_128.png"
      }
    }


    • the app/urls should end with either a / or a path. for example, don't end your url with just .com
    • web_url should point to "inside" your app, not your app's front page. The front page is usually some marketing or intro page, and you can assume the user already knows this stuff.
    • You only need to list the app/urls for your app's pages, don't worry about CDNs for your assets or AJAX calls, or whatever.
  4. Place this manifest file into my-app-rocks directory.
  5. Create a 128x128 PNG icon for your app. The Chrome Web Store docs have helpful guidance on image creation and usage.
  6. Place this icon into the my-app-rocks directory.  Make sure it's named icon_128.png, matching the name from the manifest file.
  7. Create at least one great screenshot of your app.  This does NOT go into my-app-rocks.
  8. Verify your domain with the Google Webmaster Tools.  Why?  I thought you were crazy busy?  OK, domain verification allows you to prove that you own the domain, which allows you to prove that you are publishing the "official" app for this domain.  Also, we recommend verifying your domain via DNS.  Plus, you should verify this domain with the same Google Account you intend to publish your app with.
  9. Zip up my-app-rocks to create a my-app-rocks.zip.
  10. Go the the Chrome Developer Dashboard. Sign in with the same Google Account that you verified your domain with.
  11. Click "Add New Item" and upload the my-app-rocks.zip.
  12. Enter the following required information:
    1. upload the 128 icon again (I have no idea why you need to do this).
    2. upload the screenshot of your app.
    3. choose a language.
    4. link the app to your verified domain.
    5. choose a category.
  13. Publish.
  14. Pay the $5 registration fee.
  15. Ok, now actually Publish.
  16. Done!  Yay!
Bonus Points:
  1. Integrate with OpenID and auto create your user's account.  Single Sign-On FTW!!
  2. Upload multiple app screenshots.
  3. Link to a YouTube video for your store page.
  4. Add Analytics to your store page and app.
  5. Upload a background image for your store page.
Hope this helps.  This process should really only take 30 min if you have your icon ready.  So go upload and see you in the Chrome Web Store!

Once you're in the store, there's lots more Chrome Web Store resources and documentation to review once you're not so busy.

Wednesday, September 15, 2010

Lessons Learned from Teaching Ruby on Rails 3

Aloha,

I recently taught a one day class titled Intro to Ruby on Rails 3 at the Pacific New Media school from University of Hawaii.  It was a great learning experience for me, as it was my first full day class teaching anything.

Some lessons learned:


  • I under-sold the class description, thinking it was an intro class, but in reality it was an intermediate class.  I was assuming the students knew what the Terminal was, or what a relational database is.
  • If there are basic tools required to learn the base material, spend some time explaining these basic tools, even for an intermediate class.
  • Pair programming worked about 50%.  I had the class arrange themselves in a line, from newbie to expert.  This was a great ice breaker, and I used this to group people together.  However, some people wanted to stick with their colleagues.  I saw a lot of people helping each other out and teaching each other, but not everyone is into this format.
  • There was extremely minimal lecture, and everyone appreciated it.
  • The class was very hands on, and everyone appreciated that, too.
  • I was over ambitious with the example application.  I shouldn't bother with user management at all, even though I used OpenID to minimize that problem.  Next time, skip over users.
  • The real life example app worked great.  We built an HTML5 geolocation based check-in app.
  • Ask the students to create their Heroku accounts ahead of time.  It's a quick process, but Heroku detected many accounts being created from the same IP and blocked some of the students.
  • I should have run through a final, final compliance check list.  I had elements of this, but some students missed some of the pre-req's which caused problems later.
  • I'll cater lunch next time, as there were no good options on campus and it took too long to get to the only open option.
  • I had the students sign up for Github and Heroku, but turns out explaining the benefits of Github just didn't fit into the time we had.  Next time I'll just use Heroku.
  • I wrote the whole example app ahead of time, which was invaluable.
  • We did a lot of work designing the app with class participation.  I believe this part worked.
Overall I had a great time, and I think most of the students had fun.  I'm looking forward to running the class again with the above tweaks for an even better experience.

Thanks to all who came!

Thursday, September 2, 2010

Chrome Web Store Resources and Documentation

Aloha!

The Chrome Web Store will launch later this year as a new consumer marketplace to distribute and monetize web apps.  We've collected a great set of resources and documentation to help you get started.


We encourage you to sign up as a Web Store developer and get started.  It will be an easy way to get your great web app in front of the 70+ million active users of Chrome!

Seth