User Accounts and Authentication using OAuth

Posted by Natalie J on August 13, 2019

I just created my first Ruby on Rails application that requires user accounts for access. Users may choose to create an account through the application or signup using a third-party account.

I’ll be going over how to allow your application to create an account for users that signup through a third-party and how to login users with application accounts or third-party accounts.

In this example, I am using the OmniAuth gem as the OAuth 2.0 method and Facebook as the strategy (OmniAuth refers to providers as strategies). For the full list of OmniAuth strategies, take your pick here.

Gems

Let’s get started by adding the gems you’ll need in your Gemfile.

gem 'omniauth'
gem 'omniauth-facebook'
gem 'dotenv-rails'

Run bundle install.

The second gem will depend on your strategy choice. The dotenv gem will allow you to load the variables from a .env file into ENV when the environment is bootstrapped.

Files

Next, create a new file config/initializers/omniauth.rb. This is where you’ll place your app’s OAuth credentials.

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

Create a new file .env at the root of your application. Add .env to your .gitignore file to make sure your credentials are not accidentally commited.

Get your strategy credentials and place them in .env.

FACEBOOK_KEY=pasteyourstrategykeyhere
FACEBOOK_SECRET=pasteyourstrategysecrethere

Route

To allow your user to login or signup using the third-party account, you’ll need to redirect them to /auth/:provider, where :provider is the strategy name. Provide this link on your signup and login page.

Assuming you have your user model and a SessionsController set up, add the following to config/routes.rb.

get '/auth/:provider/callback', to: 'sessions#create'

This route is where the strategy will redirect users after they have been successfully authenticated.

Custom User Class Method

After the user is successfully authenticated through the strategy, the user’s information will be available in the Authentication Hash, which can be accessed through request.env['omniauth.auth']. You can place it in a private method in the SessionsController like so.

# sessions_controller.rb

private

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

Next, create a User class method to use the information in the auth_hash. We’ll use the user’s information to find their account (if they have one) or create a new account for them (if they don’t have one).

# user.rb

def self.find_or_create_by_omniauth(auth_hash)
        self.find_or_create_by(uid: auth['uid']) do |u|
            u.name = auth['info']['name']
            u.email = auth['info']['email']
            u.password = SecureRandom.hex
        end
end

More user information provided by the Authentication Hash, with Facebook as the strategy, can be viewed here.

In the example user model above, we’re using SecureRandom.hex to generate a secure and random password for the user. If you’re using the BCrypt gem and the has_secure_password method in your user model, a user must have a password in order to be persisted to the database. SecureRandom.hex solves that problem for us.

Tip: If you’re using BCrypt and has_secure_password, don’t forget to use :password_digest in your migration, instead of :password.

SessionsController

Now, we can set up the create action in our SessionsController to handle both normal user account login and third-party account login or signup.

# sessions_controller.rb

def create 
        if auth_hash
            @user = User.find_or_create_by_omniauth(auth_hash)
            session[:user_id] = @user.id
            render 'users/show'
        else
            @user = User.find_by_email(params[:user][:email])
            if @user && @user.authenticate(params[:user][:password])
                session[:user_id] = @user.id
                render 'users/show'
            else
                flash[:message] = "Incorrect login information."
                redirect_to '/login'
            end
        end
    end

private

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

If a user uses a third-party account, an Authentication Hash will be available. if auth_hash will be true and our User class method find_or_create_by_omniauth will be triggered. The user’s information will then be available in @user and logged in.

If auth_hash is false, that means we have a normal user logging in through our application. We will attempt to find the user by their email address. If the user exists and the password submitted is authenticated (using authenticate provided to us through the has_secure_password method), the user will be logged in.

There you have it! You can now login normal users and third-party authenticated users. I hope this has helped you get OmniAuth up and running for your application!