To make that Log In button on our homepage do something, we need to set up our app on the Facebook developers portal, create a user model, and configure the rails omniauth gem. We’ll begin with registering our Facebook application at https://developers.facebook.com.

Registering A Facebook App

Navigate to https://developers.facebook.com and select Add A New App from the dropdown menu in the top right.

FB Add A New Site

Now select basic setup and fill out the form with sensible answers and submit it.

FB Basic Setup

New App ID

Now, do the same thing again, this time creating a test version.

FB Test VersionWe need a test version because Facebook only lets you register one website per Facebook application, but we want to be able to log in both on our development server and our production server.

For the test version, navigate to the Settings tab, click Add Platform and select Website. Enter your Cloud9 development server URL (which looks something like this: https://rails-workspace-alexo798.c9users.io) as the Site URL and also as a Site Domain. This will let us use Facebook authentication in development.

FB Dev Settings

Now do the same thing for the non-test-version, except this time enter your public production domain name.

FB App Settings

Keep this page open because we’re going to need the App ID and App Secret in the next section.

Omniauth Gem Setup

We need to add two gems to our Gemfile

### Gemfile

...
gem 'omniauth'
gem 'omniauth-facebook'

and run bundle install.

Now create the file config/initializers/omniauth.rb and copy the following into it:

Rails.application.config.middleware.use OmniAuth::Builder do
 provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET']
end

In Ruby, ENV is “a hash-like accessor for environment variables.” Environment variables are a way we can keep sensitive information such a private keys out of our source code. We’re going to define FACEBOOK_KEY (app ID) and FACEBOOK_SECRET (app secret) in our Cloud9 terminal, and also on Heroku. In Cloud9, use the app ID and secret from our test version application, and on Heroku use the app ID and secret from our real application.

In the Cloud9 terminal run these commands:

# Values are fake. Replace with your own. 
# On Facebook app dashboard, first is called 'App ID', second is 'App Secret'.

# Values from test Facebook app
export FACEBOOK_KEY=35828846257251
export FACEBOOK_SECRET=ee7692e65750e0ce84ae100bd43a2c2a

# Values from production Facebook app
heroku config:set FACEBOOK_KEY=9846000008287251
heroku config:set FACEBOOK_SECRET=aa44444465750e104444ce84a3aaaaa

Omniauth is now set up and ready to use. Keep in mind, you need to re-set the environment variables whenever you open a new terminal in Cloud9. This is the error you will see if you forget:

App_id required

User Model

Every person who logs in to our site will have an entry created for them in a table called Users which will look something like this:

User DB

To create this table, we’ll first generate an empty migration file called “CreateUser”. Run the following command in your Cloud9 terminal:

rails generate migration CreateUser

Now that will generate a file in app/db/migrations/ called something like 20160423072620_create_user.rb (the start is a timestamp). Open the file and add the following contents:

### db/migrations/20160423072620_create_user.rb

class CreateUser < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.string :avatar_url
      
      t.string :fb_id
      t.string :oauth_token
      t.string :oauth_provider
      t.datetime :oauth_expires_at

      t.timestamps null: false
    end
  end
end

Run rake db:migrate. Rake will find the new migration file and use it to create our User table. The integer id field is automatically created for us.

Now that our database is ready, we need to write the actual User class. In app/models/ create the file user.rb with the following contents:

### app/models/user.rb

class User < ActiveRecord::Base
  # Initializes or updates user object when logging in with Facebook
  def self.from_omniauth(auth)
    where(fb_id: auth.uid).first_or_create do |user|
      user.oauth_provider = auth.provider
      user.fb_id = auth.uid
      user.oauth_token = auth.credentials.token
      user.oauth_expires_at = Time.at(auth.credentials.expires_at)
      user.name ||= auth.info.name
      user.avatar_url ||= "http://graph.facebook.com/#{auth.uid}/picture?width=500"
      user.save!
    end
  end
end

What we’ve done here is define a class called User with a method called from_omniauth. This method either finds the record corresponding to the Facebook user that just logged in, or it creates a new record for them. We’re now going to create a sessions controller for logging in/out, and you’ll see this class and method referenced there.

Controllers

Create the file app/controllers/sessions_controller.rb with the following contents:

### app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def create
    user = User.from_omniauth(env["omniauth.auth"])
    session[:user_id] = user.id
    redirect_to root_url
  end

  def destroy
    session[:user_id] = nil
    redirect_to root_url
  end
end

Let’s look at the create action. First we retrieve the user with the method we wrote just before, User.from_omniauth. We pass in env["omniauth.auth"] because that’s just how the omniauth gem works — it puts the information returned from Facebook in an environment variable. Next we set a session variable called user_id to be equal to our new user’s id. What’s going to happen here is that when rails returns some HTML to the user at the end of this action, it’s also going to instruct their browser to store this session variable somewhere. When the user loads another page, the browser sends their session variables to our application, and we can hence identify the user. To avoid malicious users manually changing their session variables to log in as other people, the session variables are also encrypted (using a secret key kept in config/secrets.yml) when they’re sent to the user, and decrypted when we receive them.

Let’s add some routes for logging in and out:

### config/routes.rb  

...
  get 'auth/facebook', as: "login"
  get 'auth/facebook/callback', to: 'sessions#create'
  get 'logout'   => 'sessions#destroy', as: "logout"
end

The as: "login" and as: "logout" route names will give us access to convenient variables in our views and controllers login_path and logout_path, which will contain the URL’s for logging in and out.

Now we can add some methods to our application controller which give us access to information about the current user.

### app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
  private
  
    def requires_logged_in
      if current_user == nil
        redirect_to root_url #Only happens if current_user is equal to nil
      end
    end
    
    def current_user
      begin
        @current_user ||= User.find(session[:user_id]) if session[:user_id]
      rescue
        session[:user_id] = nil
        return nil
      end
    end
    
    helper_method :current_user
end

All other controllers are subclasses of ApplicationController. For example, the definition of SessionsController in sessions_controller.rb starts with class SessionsController < ApplicationController — this means SessionsController is based on a copy of ApplicationController, and we can use the methods requires_logged_in and current_user inside SessionsController. The line helper_method :current_user gives us access to current_user in our views as well.

Because our User class inherits from ActiveRecord::Base, it inherits the find(:id) method, which tries to find a record in the relevant table with the id specified, and throws an exception if it fails. An exception crashes an app unless it is handled — this is what the begin and rescue keywords are doing. Ruby first tries to execute the commands that come after begin, and if an exception is thrown, it executes the commands after rescue instead of crashing our app. We’ve also used the or-equals operator ||= here. a ||= b is equivalent to a = a || b where || represents the word or. Read it as “set a equal to b if a is undefined.” So if @current_user has already been defined by another method, it’ll just be left the way it is.

Aside: Any methods defined after the private keyword are private methods. This just means that they can’t be accessed from outside of the controller by, for example, calling ApplicationController.current_user. We’d never do this anyway, but it’s best practise to make methods private unless you have a reason not to.

Views

We can finally make some changes to the front-end of our application. Change the contents of app/views/static_pages/home.html.erb to the following:

### app/views/static_pages/home.html.erb

<div class="jumbotron">
  <h1>Welcome to Memespace</h1>
  <p>
    <% if current_user %>
      Welcome, <%=current_user.name%>!
      <br>
      <a class="btn btn-lg btn-primary" href="/logout" role="button">Log Out</a> 
    <% else %>
      <a class="btn btn-lg btn-primary" href="/auth/facebook" role="button">Log In With Facebook</a> 
    <% end %>
  </p>
</div>

I’ve highlighted the if-else statement above. The HTML between the if tag and the else tag will render if current_user exists (i.e. is not equal to nil), otherwise the HTML between the else and the end tags will render.

Now run your server with the command rails server -b $IP -p $PORT and press that Log In button and you should see your name on the homepage. Awesome. Let me show you a bit of proof that you’re actually in the database now.

Rails Console

In your Cloud9 terminal, run the command rails console. This gives you an interactive ruby console with access to all of the classes you’ve defined in your rails project. Try the command User.all, which will return a list of all of your users from the database. You should just see your own record there. This is quite handy, and you can do all sorts of stuff. Try changing your name with the following commands:

@me = User.all.first
@me.name = "New name"
@me.save

Exit the ruby console with the exit command, start your server again, and you’ll see your new name.

Keep in mind your development environment on Cloud9 and your real server on Heroku use different databases. At this point, there is still no record of you in the Heroku database. If you wish to access an interactive ruby console on the Heroku app, you use the command heroku run rails console.

To finish this chapter, push your new code to Heroku, and migrate the database there:

git add -A
git commit -am "Added user model and Facebook oauth"
git push heroku master
heroku run rake db:migrate

Tip: If you get an error on your Heroku site like this:

Rails Error

Run heroku logs in your Cloud9 terminal to see the logs (contact me if you need help).

Next Chapter

In the next chapter, we’re going to set up a page that lists all of our users, and also design some user profiles.