Dreaming of Code

Handling Multiple Social Identities in a Rails Project

March 25, 2015

I recently built a social aggregator called Full Social (full.social) built on Ruby on Rails. The idea is to associate a person's social network accounts to a single user. I found that the best way to accomplish this is to create a 1:M association between a user model and an identity model. The idenetity model is where various OAuth tokens are stored and associate them with a single user.

Configuring Omniauth

I decided to use the omniauth gem to handle the OAuth token request process. On top of the omniauth gem, be sure to also include the appropriate gems for each api (i.e. omniauth-twitter).Here's an example of a configuration file that could be used.

# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_SECRET"], { scope: "email,public_profile,user_likes" }
  provider :twitter, ENV["TWITTER_KEY"], ENV["TWITTER_SECRET"]
  provider :instagram, ENV["INSTAGRAM_KEY"], ENV["INSTAGRAM_SECRET"]
end

Handling the Callback

You'll need to add a route to your config/routes.rb file to handle the callback that is made after a user approves your app for use.

# config/routes.rb
get '/auth/:provider/callback' => 'sessions#create'

In my particular case, I'm also using the user's social networks to act as a log in. We will create a sessions controller to handle this.

# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def create
    if auth_hash
      identity = Identity.where(provider: auth_hash.provider, uid: auth_hash.uid).first
      if identity
        user = User.find_by(id: identity.user_id)
        identity.update_attributes!(
          token: auth_hash.credentials.token,
          secret: auth_hash.credentials.secret,
          expires_at: expires_at
        )
      else
        user = logged_in? ? current_user : User.create(name: auth_hash.info.name, email: auth_hash.info.email)
        Identity.create(
          provider: auth_hash.provider,
          uid: auth_hash.uid,
          token: auth_hash.credentials.token,
          secret: auth_hash.credentials.secret,
          expires_at: expires_at,
          user_id: user.id
        )
      end
      session[:user_id] = user.id unless logged_in?
    end
    redirect_to root_path
  end

  private

  def auth_hash
    request.env["omniauth.auth"]
  end

  def expires_at
    if auth_hash.credentials.expires_at.present?
      Time.at(auth_hash.credentials.expires_at).to_datetime
    elsif auth_hash.credentials.expires_in.present?
      DateTime.now + auth_hash.credentials.expires_in.to_i.seconds
    end
  end
end

It could use a bit of refactoring, but it gets the job done. If a user has never been to the site before, then they pick a provider to log in with, a user is created, and an identity is created for that particular provider and associates the Identity to that user. Once the user is logged in, they can now connect other social accounts and new identities are created and associated to the current user. That user can now log back in once their session is expired with any identities that have authorized the application.