a few bits on flickr_fu
For integrating flickr into photolog.at I decided to give flickr_fu a shot as rflickr is not maintained as it used to be. A warning: fickr_fu isn’t that complete, e.g. blogs are missing but it should be too hard to extend and push the patches to the hub.
Like with OpenID I want to integrate flickr the hard way: Assigning multiple accounts to one of my users. I’m not sure if there are many users who have more than one account, but I’m sure for groups of artists it will be just the perfect tool.
your credentials
There are a few things you have to understand and know when working with the flickr API. First, you credentials:
- API key: you can request three different kinds of API keys: For Desktop, Web or Mobile apps. Of course I take the web one and of course it’s the worst possible…
- Secret: An additional security measure, dunno why at all
- frob: Looking up that word is a time-waster and if you don’t handle with the right frob you are wasting time. For all but the web app you won’t need the frob but for the web app you need one and you get when the user grants access to your app and is being redirected to the callback url that you setup with the API key. Sadly this callback url is not at all configurable on runtime, but I’ll come back on that later.
- token: Once you obtained the frob you can store this token, hopefully forever. Tokens are per-user and you should run checkToken if a request fails
problems with has_many
As I said, more than one flickr account per user. Which is bad as I cannot customize the callback url on the fly, adding a parameter to find the account that is being processed. This problem bothered me half an afternoon and the solution is simply to try the frob returned from flickr on all accounts the user has setup with my app. Normally that’s one account anyways and you can sort descending by updated_at to speed up the process.
You can test the accounts by simply trying to get the token and once you have a hit, store the token and forget about the stupid frob.
...to be continued…
OpenIdAuthentication in detail
I’m currently working on my own little project to fill photolog.at with something interesting and for this I decided to use OpenID only. Dr. Nic is a big fan of OpenID and of course I hearted his advice of allowing one account with many identities
The first OpenID plugin I tried was OpenIdAuthentication which would also support regular loging and I can say: it’s just perfect for me. The reason why I decided to allow OpenID login only is that the application will closely integrate into flickr, which is via Yahoo, one of the many OpenID providers. This way I save the obviously unnecessary authentication system, and a bunch of fields on the User model.
How OpenID works
The idea is very simple, you come to my website, type in your OpenID url, my website will forward you to your OpenID provider and there you might have to login. The OpenID provider then sends you back to my website and if nothing went wrong my website will know a few basic details about you, create an account if not existing yet and you are logged in. It’s very easy and quick, no need to type in data that you already stored at your provider.
In my webapplication most of those steps are automatically done by the OpenIdAuthentication plugin, but if you are sophisticated then you might want to change a few things from the example in the README. For instance, it doesn’t tell you anything about creating a new user, or – Dr. Nic’s favourite – how to assign multiple identities to one user. I’ve added all of those functions to my version of the SessionsController. But first a few lines from the models.
Models
The User model is quite simple, only login, name and email. All three will be gathered from the OpenID provider but you might want to add a edit form.
To comply with the mulitiple identities requirement I’ve added an Identity model which contains the identity_url (this quite replaces password and login in a normal user model) and user_id. A user has_many :identites and each Identity object belongs_to :user (just for the case you still have to look this up…).
The OpenIdAuthentication Plugin adds two models, OpenIdAuthentication::Association and OpenIdAuthentication::Nounce but we don’t touch these.
The SessionsController
Best is to follow OpenIdAuthentication’s README and then replace the SessionsController. Please read me comments and let me know what you think about it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
class SessionsController < ApplicationController # sadly necessary skip_before_filter :verify_authenticity_token, :only => :create def create if using_open_id? # first difference: we explicitly pass the parameter. Much cleaner anyways than hiding it in the plugin... open_id_authentication(params[:openid_identifier] || params[:openid_url]) end end protected def open_id_authentication(identity_url) # also possible for :required and :optional are %w( dob gender postcode country language timezone ) # have a look here: http://openid.net/specs/openid-simple-registration-extension-1_0.html authenticate_with_open_id(identity_url, :required => [ :nickname, :email ], :optional => :fullname) do |result, identity_url, registration| if result.successful? # successful request, time to check wether the identity already exists identity = Identity.find_or_create_by_identity_url(identity_url) if identity.user.nil? # identity doesn't exist, but maybe the user? We use email as a unique key for this. if user = User.find_by_email(registration["email"]) logger.debug("using user '%s'" % user) # assign identity to an existing user identity.user = user identity.save else logger.debug("creating new user '%s'" % registration["nickname"]) # create a new user for the identity. identity.user = User.create!(:login => registration["nickname"], :name => registration["fullname"], :email => registration["email"]) identity.save end end # log the user in @current_user = identity.user successful_login else # oops. failed_login result.message end end end private def successful_login session[:user_id] = @current_user.id redirect_to(root_url) end def failed_login(message) flash[:error] = message redirect_to(new_session_url) end end |