#68
Aug 27, 2007

OpenID Authentication

Ever wonder how to implement OpenID authentication in your Rails app? This episode will show you how to add it to a site with an existing authentication system.
Download (27.7 MB, 11:09)
alternative download for iPod & Apple TV (16.4 MB, 11:09)

Resources

# routes.rb
map.open_id_complete 'session', :controller => "session", :action => "create", :requirements => { :method => :get }

# session_controller.rb
class SessionController < ApplicationController
  # render new.rhtml
  def new
  end

  def create
    if using_open_id?
      open_id_authentication(params[:openid_url])
    else
      password_authentication(params[:login], params[:password])
    end
  end

  def destroy
    self.current_user.forget_me if logged_in?
    cookies.delete :auth_token
    reset_session
    flash[:notice] = "You have been logged out."
    redirect_back_or_default('/')
  end
  
  protected
  
  def open_id_authentication(openid_url)
    authenticate_with_open_id(openid_url, :required => [:nickname, :email]) do |result, identity_url, registration|
      if result.successful?
        @user = User.find_or_initialize_by_identity_url(identity_url)
        if @user.new_record?
          @user.login = registration['nickname']
          @user.email = registration['email']
          @user.save(false)
        end
        self.current_user = @user
        successful_login
      else
        failed_login result.message
      end
    end
  end
  
  def password_authentication(login, password)
    self.current_user = User.authenticate(login, password)
    if logged_in?
      successful_login
    else
      failed_login
    end
  end
  
  def failed_login(message = "Authentication failed.")
    flash.now[:error] = message
    render :action => 'new'
  end
  
  def successful_login
    if params[:remember_me] == "1"
      self.current_user.remember_me
      cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
    end
    redirect_back_or_default('/')
    flash[:notice] = "Logged in successfully"
  end
end
<!-- session/new.rhtml -->
<label for="openid_url">OpenID URL</label><br />
<%= text_field_tag "openid_url" %>
/* embeds the openid image in the text field */
input#openid_url {
   background: url(http://openid.net/login-bg.gif) no-repeat;
   background-color: #fff;
   background-position: 0 50%;
   color: #000;
   padding-left: 18px;
}

RSS Feed for Episode Comments 55 comments

1. A Aug 27, 2007 at 08:07

Nice article, Thanks.

You should consider increasing the width of your website. almost everyone has a wide screen monitor these days, and on my monitor 2/3'rds of your site is white space.


2. Gustavo Beathyate Aug 27, 2007 at 08:49

yeah, I agree with A about the website width, anyways if you use google analytics you can know that for sure.


3. Josh Aug 27, 2007 at 09:12

Could always turn off stylesheets and just read the plain text :) But increasing the width wouldn't be a bad idea.

Great job on the screencast, love 'em! Always informative and great topics!


4. Darryl Ring Aug 27, 2007 at 09:37

Actually, from a readability standpoint, this width is nearly perfect. The with of the main column is about 60 characters--6 less than is considered optimal.

Read: http://webtypography.net/Rhythm_and_Proportion/Horizontal_Motion/2.1.2/


5. Dave Macpherson Aug 27, 2007 at 11:07

Maybe I missed something, but is a user record created in the database upon successful Open ID authentication? What is the user ID of the user that is created? Is it the nickname provided by the Open ID authenticator? What if the nickname conflicts with an existing user name already in your Users model?


6. Ryan Bates Aug 27, 2007 at 11:17

@Dave, a new user model is automatically created and saved if they don't already have an account. If they already have an account then it just fetches the record from the database instead of creating it.

This behavior is not required when using OpenID, it is just one way to do it. Alternatively you may want to redirect to the signup page with some fields prefilled.

The user ID is generated automatically through auto-incrementing like all other Rails models.

Currently it doesn't complain if there's already a user with that name. That's because I disabled validations with save(false) call. However, this is just a temporary solution, how you want to handle validations is up to you. One solution is to redirect to the signup page.


7. Todd Breiholz Aug 27, 2007 at 11:44

Nice job, as usual, Ryan.

Does this handle the use case where the user is already registered on your site, but then decides to log in using OpenID? It looks like in that case, you'd get an additional user added to your DB because the find_or_initialize_by_identity_url would not return anything.


8. Chris Kampmeier Aug 27, 2007 at 12:18

Thanks Ryan. Just added OpenID to my new app.

Beware that if you're not going to automatically create users on-the-fly like Ryan does, you'll need to make sure that when a user adds an identity URL to his record manually, it gets normalized.

Authentication won't work if the value in the DB isn't normalized (e.g. missing a trailing slash). I accomplished this with a before_validation callback: http://pastie.caboo.se/91417


9. MKA Aug 27, 2007 at 20:24

a quick pointer:

in your routes, make sure you put the map.open_id_complete *before* the map.resource :session which is already there.

the resulting error shows up as an un-intuitive #show call on the SessionController


10. Ryan Bates Aug 27, 2007 at 23:57

@Todd, Good point. If they try to log in through openid when they have an existing account it will create another user.

To get around this problem, add some instructions telling them to log in normally and edit their account profile. Here they can set their openid url.


11. Heiko Aug 28, 2007 at 01:16

I've gathered some information about OpenID security at www.rorsecurity.info .
See my name above for a full link.


12. James Aug 28, 2007 at 05:45

Hi, Ryan.

One episode that I want see is about navigation (include nested navigations/level navigation) like the TabNav-plugin.


13. Dave Macpherson Aug 28, 2007 at 10:22

Thanks Ryan. It seems to me really that since a user can have multiple Open ID's, really one ought to allow a user to register additional ones against their user account (has many relationship, not an attribute in the User model) so as to prevent multiple User records being added (if that's a concern, of course).


14. Ryan Bates Aug 28, 2007 at 11:03

@Dave, I kept one OpenID per user for simplicity, but you can certainly put it in a separate model and set up a one-to-many association with user.


15. Glenn Nilsson Aug 28, 2007 at 14:32

MKA: Thank you! Got the same mysterious problem here too.


16. Kazi Rameez Sep 01, 2007 at 07:15

Just downloaded and expectations are same for it too. Videos tutorial always very affected than reading paper/e-paper books.

Now, the collection is getting bigger so I thought or imagine there should be a rating bar people start rating their best tutorials and new user could easily find the best ones.

Best of Luck


17. Grant Neufeld Sep 03, 2007 at 00:31

This and the restful_authentication Railscasts were both very useful. Thanks!

Would like to see a follow-up showing open-id login of new user going to sign-up form instead (to allow additional required fields to be entered/validated: e.g., email, city, real/screen names, etc.).

Would also like to see a deeper exploration of getting information from the user’s OpenID source. I know that some OpenID servers provide access to additional info fields, but haven’t figured that part out yet.

Finally, I’d like to see a way to turn around and offer a user’s login on my server as an OpenID service for other websites the user wants to access.

Heh. I guess I’m asking a lot :-) These are just suggestions and I’ll look forward to your future Railscasts regardless of whether you cover these topics or not.

Thanks again!


18. Roland Moriz Sep 27, 2007 at 10:15

Hi,

one quick note: the css should probably be
input#openid_url { ... } since it's an id not a class name.

thanks for your great screencasts!
Roland


19. Ryan Bates Sep 28, 2007 at 14:12

@Roland, thanks. Fixed. :)


20. topfunky Sep 30, 2007 at 17:41

Very useful!

This screencast and code makes the OpenID part easy. It's the integration with an existing system, and writing specs that takes a bit more work.

The article by Ben Curtis was helpful. I also ended up stuffing a bunch of the code into the User model: authenticate_by_openid(identity_url, registration). This takes care of the logic in the model so it can be reused from a signup form as well as the login form.


21. topfunky Sep 30, 2007 at 20:26

Ok...one more. Here's a sample of an RSpec method to mock the OpenID request. Some work is needed, but it covers the basics.

http://pastie.textmate.org/102377


22. Corrigan Oct 09, 2007 at 16:33

I am wondering a bit about how people are handling duplicate nick names. What pattern are you using in your app in order to prevent this from happening. I considered just letting validation fail the attempt to save the user object, but the problem is that some open ID providers (Verisign) allow you to implement a remember me functionality, essentially skipping over the login process on a subsequent login, and sending the same sreg information. This makes it difficult for a user to try a different nick name.


23. Ryan Bates Oct 10, 2007 at 08:11

@Corrigan, what I have done in the past is store the sreg parameters in a session and redirect to the registration page. This allows the user to complete the registration process and choose a different name if they need to. I'll see if I can put some code up somewhere and I'll add a link here for it.


24. Josh K Nov 14, 2007 at 07:36

Instead of passing false to the save method, I just modified the password_required? method in the user model.

It now reads:
(crypted_password.blank? && identity_url.blank?) || !password.blank?

Is doing something like that ok?


25. Josh K Nov 14, 2007 at 07:56

Whoops! Should have tested that before posting... Now when a user signs up, they could send a post request with anything in the indentity_url field and not have to put in a password.


26. Ellis Berner Dec 17, 2007 at 07:40

@Ryan, have you found a permanent solution to creating the record instead of save(false)?


27. jDeppen Dec 23, 2007 at 18:09

When using this is there anything different for Rails 2.0.2 (specifically cookie-based session stores)?

Also, I keep getting 'uninitialized constant OpenID::Store'
I looked at http://drnicwilliams.com/2007/07/26/sample-app-rails-multiple-openids-per-user/
and there is a 'gems' directory in vendor which contains ruby-openid-x.x.x and there's 'stores.rb' inside.

Still getting the error, any ideas?


28. Jai-Gouk Jan 01, 2008 at 22:01

I've found a patch from here(http://dev.rubyonrails.org/ticket/10604 ) and downloaded it.

after that,moved to
vendor/plugins/open_id_authentication

$ patch -p0 < update_openid_plugin_to_ruby_openid_2.diff

It worked!


29. Aerpe Jan 09, 2008 at 17:52

@Jai-Gouk, thanks alot for that information about the patch. /@

@All

I get the following error after returning to the website with OpenID authentication.

"Unknown action - No action responded to show"

Anyone got an idea?


30. Jai-Gouk Jan 15, 2008 at 10:42

Well, it seems that Rails2.0's restful controller.
I put some code to show like this,
-----------------------------
params(:result) do |status, identity_url, registration|
case status
when params(:result):missing
failed_login "Sorry, the OpenID server couldn't be found"
when :canceled
failed_login "OpenID verification was canceled"
when :failed
failed_login "Sorry, the OpenID verification failed"
when :successful
@current_user = User.find_by_identity_url(identity_url)


31. Jai-Gouk Jan 25, 2008 at 05:16

Hi, I've been googling about a week to solve a ruby-openid problem. And found a working example on Rama McIntosh's blog. http://myutil.com/2007/12/29/openid-2-0-2-with-rails-2-0-2
At first, I thought rails Ticket #10604 patch worked. But it was more complicated than that. Since Rails, ruby-openid, and open_id_authentication plugin versions were not consistent. And Rama McIntosh went through all. You can find Dr. Nick’s example application fully ported to rails 2.0.2.

Here is my development environment.
------------------------------------------------------------
LG LW25 advanced laptop IntelCore2 T5600, Mem 2G
XP Pro version2002 SP2
Cygwin + CygPutty
Ruby ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-cygwin]
Rails 2.0.2
ruby-openid (2.0.3)
rake (0.8.1)
mongrel (1.1.3)
mongrel_cluster (1.0.5)
ruby-yadis (0.3.4)
app db : sqlite3
------------------------------------------------------------
Besides his README, some extra typing was needed to run the app.
1. svn co http://drnicwilliams.com/svn/openid/demos/apps/openidauth_multiopenid/trunk openidauth_multiopenid
 
2. Do not erase some svn directories because he made some rake tasks related with svn.
3. rake freeze TWICE!
4. mkdir log
5. require_gem => gem
openidauth_multiopenid/vendor/gems/ruby-openid-1.1.4/lib/openid/service.rb:7
/home/yunsoo/auth_openID/openidauth_multiopenid/vendor/gems/ruby-openid-1.1.4/lib/openid/discovery.rb:8:
6 rake db:create
7. gem install mocha
8. rake test
9. config/environment.rb
  config.action_controller.session = { :session_key => "_openidauth_multiopenid_session", :secret => "mocramagic" }
  
  Replace to other key, secret like this,
  
    config.action_controller.session = {
    :session_key => '_foo_session',
    :secret => '4747ba80asdfqwertwertwee5y4365345ertf gadrtvrwebterwvd653b65tyt93c45cb53242eb43'
  }
10. mongrel_rails start!!


32. Rob O Jan 28, 2008 at 00:08

Does anyone know if these instructions work with the new version of the ruby-openid gem (version 2.0.3)?


33. hendrik Feb 17, 2008 at 05:16

"Tied plugin to ruby-openid 1.1.4 gem until we can make it compatible with 2.x [DHH]"

14th Feburary 2008

http://dev.rubyonrails.org/changeset/8872

P.S.: Thank you very much for Railscasts!


34. Dave Sherratt Feb 20, 2008 at 01:19

Great screencast, although after installing it i'm getting a "Install the ruby-openid gem to enable OpenID support" error even though the gem is definitely installed. any ideas?


35. Jonathan Feb 23, 2008 at 07:04

Hi,

I've implemented OpenID authentification like described in Ryan's tutorial and got a error like this:
NameError in SessionsController#create

uninitialized constant OpenIdAuthentication::OpenID

Can somebody help me?

Greetings,
Jonathan


36. Brandon Feb 27, 2008 at 11:12

I am having the same problem as Jonathan with the NameError.


37. Amit Mar 05, 2008 at 01:18

Add
require "openid"
at the top of your controller

Regards,
Amit


38. Tom Wade Mar 11, 2008 at 08:17

>>You should consider increasing >>the width of your website. >>almost everyone has a wide >>screen monitor these days, and >>on my monitor 2/3'rds of your >>site is white space.

I strongly disagree. What you don't see is the negative effect this has on those people who must stay with a 680x800 screen for reasons of poorer vision. Making the text wider requires us to continuously scroll left and right, which is really aggravating.


39. jc Apr 20, 2008 at 20:19

I think with this there should be a step right after initial OpenID auth that is a CAPTCHA before the account is created to reduce the possibility of spam. This is critical for anyone adding OpenID authentication to his/her system.
OpenID is not inherently trustworthy.

So:

1. User goes to login page
2. User logs in with OpenID
3a. If it's his first time logging in redirect to a CAPTCHA and then, if successful on CAPTCHA, create the account and log the user in.
3b. If he is already on the system authenticate without the CAPTCHA and log the user in.


40. jc Apr 20, 2008 at 20:25

My last comment isn't meant to say that this should've been included in the tutorial; I realize it's definitely in the realm of a separate discussion/tutorial. It's just a suggestion for those actually wanting to implement OpenID.

Everyone should be aware that there is a level of trust that needs to be established before allowing any user to login via OpenID, just as with normal authentication. Trust could be established with a CAPTCHA, whitelist, email verification or other methods.


41. Joe C Apr 21, 2008 at 12:55

Wow this was a critical failure for me, but I can't see where I went wrong. I tried using the openid railscast on top of the restful_authentication using the stateful engine.

Everything seemed to work until I tried logging in. The engine went out to my openid provider and when control was passed back to my app it was looking for session/show.html.erb.

It never seems to run open_id_authentication.


42. Joe C Apr 21, 2008 at 14:32

Retried this tutorial after setting up restful_auth as in episode 67.

I get the error when trying to execute a login using my openid:
Unknown action

No action responded to show

Something is getting confused with the routing within the controller and is trying to call the show action. Anyone run into this or find a work around?


43. Joe C Apr 23, 2008 at 06:30

Important Safety Tip:
The custom route:
map.open_id_complete 'session', :controller => "session", :action => "create", :requirements => { :method => :get }

Should be placed BEFORE the main route:

map.resource :sessions

When I switched these, everything started working ok


44. Adam Apr 24, 2008 at 23:31

For some reason as soon as I followed this screencast I get this error. Anyone have any ideas?

I go to /login use the openid then I get this.

uninitialized constant SessionsController::User


45. Aeg May 09, 2008 at 11:57

I'm getting that error too

uninitialized constant SessionController

any idea whats causing this?


46. Aeg May 09, 2008 at 12:34

Ok If you guys are having the problem i listed above pay attention:

Look at the map.open_id_complete line in your code. You might need to change :controller => "session" to :controller => "sessions"

it should look like this, for me anyway

map.open_id_complete 'session', :controller => "sessions", :action => "create", :requirements => { :method => :get }

I hope this helps people out.


47. kino May 23, 2008 at 01:57

The things in themselves stand in need to, even as this relates to reason, the transcendental objects in space and time, as is shown in the writings of Galileo.


48. snowmaninthesun Jul 09, 2008 at 15:05

So, if openID works with your app, but you're having problems dealing with giving your openID users, a user name, here is what i did.

Following RESTful Authentication with OpenID, allow your user to login to your site using their openID.

Redirect them to a profile page, prompting them to change their e-mail/username/whatever-else-you- want. I also added an observe_field to alert the user whether the desired name had already been taken. Then, when passing the information to the current_user, before you can current_user.save! it must have a password.

So, generate a random number, and or string. Then simply set this as the password, you can then e-mail it to the user (in theory i haven't tested this out yet), if they want to change it, but it doesn't matter if they only login using openID.

this works great for me, let me know if i'm doing something horribly wrong. This way our openID users can get and change their usernames while not allowing anyone to create a user without a password.


49. Kevin Kaske Sep 29, 2008 at 09:06

After following this tutorial I'm having trouble. I cannot get the "remember me" function to work. Has anyone else experienced this?

Great videos, Ryan! Keep up the great work!


50. George Oct 25, 2008 at 10:40

Hey, I have a great suggestion, what about mention on every episode that it is obsolete (if it is)? So, there won't be anymore stupid guys like me, trying to do like you describe in the episode and finding out that your code doesn't work with latest stable version. I can help you starting ranging the episodes: this one is obsolete!


51. aion money Nov 20, 2008 at 18:21

Thanks Ryan,I think this is one of the most wonderful sites. I have great admiration for you.


52. buy Hellgate Palladium Nov 22, 2008 at 00:27

I have great admiration for you.


53. lily Nov 27, 2008 at 19:23

Thank you Ryan, your screencast is good. Please look at our URL, if necessary we can learn from each other.


54. Jim Meyer Nov 29, 2008 at 18:39

Made my way here from Tardate 11.1[1] ... all worked as advertised (Rails 2.2.2, restful_authentication and open_id_authentication plugins). There was some minor breakage in open_id_authentication and request.relative_url_root which Josh fixed in Sep 2008[2] but hasn't made its way into the default plugin install. Don't know why @George had a hard time with it; he didn't give much info to go on.

Thanks, Ryan, for the excellent screencasts. I have great admiration for you. ;]

--j

p.s. @Darryl (comment #4): Thanks for the link to http://webtypography.net ... an excellent site I didn't know about.

[1] http://tardate.blogspot.com/2008/10/restfulauthentication-and-openid-with.html
[2] http://github.com/rails/open_id_authentication/commit/00d8bc7f97b9a3113e004475b63dbf84f5397237


55. wow patch 3.0.4 Dec 03, 2008 at 19:04

good, thanks for your infos

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player