Logging in now works

If a picture is worth a thousand words, this post has three thousand fifteen words:

sano-013

sano-014

sano-015

Advertisements

Getting the current user

I’m not using any authentication gem or plug in, I’m rolling my own. I’m actually using OpenID, so rolling my own is not hard: no passwords to encrypt, resend and send, no email address to verify. I think this is the future. But I’m also doing something that I call ghost users.

When you use one of my web apps you can start using most of it right away. At first you are a totally anonymous user but as soon as you do something that requires you to have an account on the database I create one for you. In the case of Sano, the moment you add a weight you’ll have an account.

At that moment your account is accessible thanks to the session of your browser. The moment you close the browser that account is no longer accessible. If you use the app and like it and decide to stick around, all you have to do is log in with your OpenID. For using the web app there’s no barrier of entry.

From the programming point of view this creates some challenges:

  • Maintenance of the users and related data. Over time the user database will have users that are no longer accessible with weights and other related data. That needs cleaning up. For the moment I’m going to ignore that problem because it has no impact on the system and it’s not likely to present a problem any time soon.
  • Getting a user object is no longer a query to the database. Any piece of code should be able to get a user object no matter what’s the status of the session. Sometimes the user object is only needed in memory and sometime it’s needed in the database. For example: if you create a new weight, I need a user object that’s been saved on the database; to list your weights, I need a user, I don’t care if it’s in the database or not (if it’s not, it’s not going to have any weights, which is where you start).
  • Merging users happens regularly. Look at this scenario: you go to the app, you log in, you enter your weight, you leave; you go back to the app, you don’t log in, you enter a new weight. Now you have one account with one weight and another session only account with another weight. You realize you need to log in and you do it. At that moment I have to merge the two users.

To make it as simple as possible I’ve created a method called user in my application controller that is also declared as a helper method so it’s available for all controllers and all views. This method is guaranteed to return a user, and if you call with options[:create]=true then it’s guaranteed to return a user that is also on the database. What is not guaranteed is that the user object represents a permanent account. It may be what I call a ghost user.

This is the method:

class ApplicationController < ActionController::Base
  helper_method :user
  helper_method :user!

  protected

  # Helper method to get the current user. It will always return a user but the
  # user may not be in the database. If options[:create] is true, then the user
  # will be in the database (although it may be a ghost user).
  def user(options = {:create => false})
    # If we already have a user object, return that.
    return @user if @user != nil

    # Try to find the user in the database if session[:user_id] is defined.
    @user = User.find(session[:user_id]) if session[:user_id] != nil
    return @user if @user != nil

    # Create a new user object.
    @user = User.new()
    if options[:create]
      # Save the user in the database and set the session user_id for latter.
      @user.save
      session[:user_id] = @user.id
    end
    return @user
  end

  # Helper method to get a user that for sure exists on the database.
  def user!
    return user(:create => true)
  end
end

Profile models

The profiles in Sano are built out of two models:

  • User
  • OpenId

The reason to have an OpenId model and not just one field in the User model is that I like giving users the possibility of defining more than one OpenId provider. StackOverflow does that and since I use my own OpenId server, the day it was down, I was really grateful I could still use my myOpenId account to log in.

So I created the two models:

./script/generate model user name:string email:string height:float gender:boolean birthday:date
./script/generate model open_id user_id:integer identifier:string display_identifier:string

and then I used Matthew Higgins’s foreigner to define the foreign keys. The end result follows.

Migration for users:

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :name
      t.string :email
      t.float :height
      t.boolean :gender
      t.date :birthday
      t.timestamps
    end
  end

  def self.down
    drop_table :users
  end
end

Migration for OpenIds:

class CreateOpenIds < ActiveRecord::Migration
  def self.up
    create_table :open_ids do |t|
      t.integer :user_id, :null => false
      t.string :identifier, :null => false
      t.string :display_identifier
      t.timestamps
      t.foreign_key :users
    end

    add_index :open_ids, :identifier, :unique => true
  end

  def self.down
    drop_table :open_ids
  end
end

Both models:

class User < ActiveRecord::Base
  has_many :open_ids
end

class OpenId < ActiveRecord::Base
  belongs_to :user
end

A thousand more words:

sano-002

The advantages of OpenID

forgot-passwordOpenID has many advantages. For the average user, the main one is not having to remember a thousand passwords. That’s obvious. But also consider not having to remember the username. Many web sites use the email address as username and that’s nice, but many don’t. And most people are not lucky enough to have a username that’s free everywhere. For my wife, remembering the username is sometimes as hard as remembering the password.

Not having to worry about poorly programmed web sites leaking your password because they stored it in plain text and they have phpMyAdmin open without any password is also a big plus, but not something the average user would see.

But for the developer, it has many, many advantages.

Not having to decide on what identifier to use for your users (users vs emails vs ids). Not having to implement a log in screen, which means not having to worry about SSL encryption which means not having to get a dedicated IP address, a certificate, configure the web server accordingly and ensure that the site switches to https when it must.

Not getting password for the user means you don’t have to store a password. You don’t have to figure out what is the appropriate encryption mechanism so that if your encrypted password leak, they are not readable. Not using plain text is not enough, as some encryption mechanisms are easily broken. Not having to worry about that is huge.

You don’t have to create a signup page, people just log in. You don’t have to validate the password by asking for it twice or validate its strength or any other stuff like that.

You don’t have to create a remember password page, which means one less place where you have to deal with sending emails. That’s always good. Also it means that you don’t need to store the email of the user. You may want to, but that’s your option.

I’ve always been a fan of canned authentication and authorization systems. I’ve been using them since the days of PHP 4.0 and I used them in Django and ASP.NET (MVC). But with OpenID, it seems the authentication became almost trivial. Canned solutions were always troublesome because they had to work for everybody so they implemented a lot of stuff you don’t actually need and sometimes you spend more time fighting the bureaucracy of the system than producing something.

Is it possible that without OpenID authentication and identity for the developer of a web site becomes something simple and trivial? Where rolling your own solution not only is simple enough, but also the way to go. I’m looking forward to my users being just in the user table, and not all over the place in users, profile, membership, etc. I’m giving the roll-your-own-with-OpenID a try. I hope to post positively about it soon.

Converting the ASP.NET MVC project into OpenID

When you create an ASP.NET MVC project it comes with a controller called AccountController that manages logging in, logging out, registering, changing password and so on. Since usernames and passwords are dead I converted it into OpenID and I’m just pasting it here for everybody to use.

I’m using the DotNetOpenAuth library which you have to download, put in your project and refer. The difference between what I’m pasting and the example provided by DotNetOpenAuth is that I’m actually storing the user in the membership database, like the original AccountController.

My work is based on the on the blog post Adding OpenID to your web site in conjunction with ASP.NET Membership. I really had to put a couple of hours on top of that, so I consider it worth it to post it. Scott Hanselman also provides useful information for integrating OpenID. I’m using the jQuery OpenID plug-in but I’m not going to post my views. They are really trivial and left as an exercise to the reader.

I’m not using any extra tables, I’m storing the OpenID identifier (the URI) in the field for the username. This has the advantage of not requiring any other fields but the disadvantage that you can have only one identifier per user. There are some unfinished parts but since you are likely to customize them anyway, I don’t feel too guilty about not finishing yet. If you find a bug, please, let me know.

Continue reading

I will stop using web sites that don't support OpenID

With so many OpenID providers like MyOpenID, Google, Yahoo, AOL and more, with so many OpenID consumers, and so much software like WordPress, Plone supporting OpenID out of the box or with an easily installable plug in and with so many libraries like DotNetOpenID, JOpenID, Google App Engine Django OpenID, etc. I think we are ready to use OpenID everywhere.

I want to give my little push to the revolution, so here we go:

Reviewed by Daniel Magliola. Thank you!