Blog

How to add an Authlogic hook to record login attempts

Jonathon Horsman Oct 19 2 comments

We want to record all login attempts to our application, both successful and unsuccessful, using Authlogic.

It turns out this is quite easy. I was originally trying to hook into the Authlogic callback, such as before_persisting, before_create, etc.

Unfortunately many of these callback are called multiple times on each login, and the *_create callbacks are only called on successful logins, so failed logins are not recorded.

The solution is to subclass the Session save method.

Create a new table

Assuming you already have a User table with the standard login, crypted_password, password_salt and persistence_token fields, you first create a table for recording login attempts:

@
script/generate migration AddLoginsTable
@

and add the following content:

@ ruby
class AddLoginsTable < ActiveRecord::Migration
def self.up
create_table :logins do |t|
t.string :login
t.integer :user_id
t.timestamp :created_at
end
end

def self.down drop_table :logins end

end
@

This simply acts as a log of login attempts.

Override save in the UserSession

Using standard username/password logins, you should have a UserSession model class, which subclasses Authlogic::Session::Base.

Simply add the follow save method:

@ ruby
class UserSession < Authlogic::Session::Base

def save(&block) success = super Login.create({:login => login, :user_id => user ? user.id : nil}) success end

end
@

Here the username is recorded in the login field, and corresponding user is set in the user_id column.

If the login failed (due to invalid password, deactivated user, etc) then this will be null.

I deliberately don’t capture the password because I don’t want to be storing user password in plain text, even if they are incorrect.

You may be wondering, why didn’t I just do this?

@ ruby
Login.create({:login => login, :user => user}) # this breaks my tests!
@

I tried this and got infinite loops in my tests, where the user object had not yet been persisted to the database.

This is because creating a login object with a user object which has not yet been persisted causes the user object to be saved, which in turn calls save on my user session.
And this of course results in the loop.

Any questions? Please leave a comment

Comments //

Fedya

Fedya May 18

I'm using ProteMac LogonSentry to record logon attempts. It's the special software
http://protemac.com/

Michal

Michal May 19

Thanks for this!

I went ahead and followed your tutorial. I was getting the error "NameError (uninitialized constant UserSession::Login):" because I assumed the "Login.create" call would be something built in to authlogic.

I tried making a sub controller (under the user_session controller) to define it, but that just opened up a new can of worms. Did you go ahead and create a login_controller etc to define it? Or am I missing something?

Post a comment

  1. optional

Recent Tweets

Blog: The monumental Myspace cock-up: http://bit.ly/emgRKV
Tweeted on Friday at 09:43

Awww railsapi, delete some logs: http://bit.ly/htBNDH
Tweeted on Wednesday at 16:15