back end dev
OAuth with Rails
Authentication Flows in a Rails App

Rails Social OAUTH Login

Setup

rails new <app name> --database=postgresql

Postgres

Using the custom multiple database postgres docker image:

docker run -id --name rails-db -e POSTGRES_MULTIPLE_DATABASES="railyard-dev","railyard-test","railyard-prod" -e POSTGRES_USER=<database user> -e POSTGRES_PASSWORD=<password> -p 5434:5432 db:latest

Rails

Config

Needs:

The callbacks are:

http://localhost:3000/users/auth/github/callback
http://localhost:3000/users/auth/twitter/callback
http://localhost:3000/users/auth/google_oauth2/callback

Twitter required extra permissions under the permissions tab, Check the "request user email" box.

Initial setup:

#/.env

GITHUB_APP_ID=
GITHUB_APP_SECRET=
TWITTER_APP_ID=
TWITTER_APP_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=

MAILER_DOMAIN=
MAILER_USERNAME=
MAILER_PASSWORD=

PUBLISHABLE_KEY=
SECRET_KEY=
echo "
gem 'rspec-rails'
gem 'devise'
gem 'omniauth-github'
gem 'omniauth-twitter'
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
gem 'dotenv'
gem 'activerecord-session_store'
gem 'mailgun-ruby'
gem 'stripe'
gem 'jquery-rails'" >> Gemfile

bundle install
#/config/database.yml

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 5
  timeout: 5000
  username: worker
  password: S0itg0es1
  host: 127.0.0.1
  port: 5434

development:
  <<: *default
  database: railyard-dev

test:
  <<: *default
  database: railyard-test

production:
  <<: *default
  database: railyard-prod

Home Page:

rails g controller home index

Edit config/routes.rb:

Rails.application.routes.draw do
  root 'home#index'
end
#/app/views/layouts/application.html.erb
<p class="navbar-text pull-right">
<% if user_signed_in? %>
  Logged in as <strong><%= current_user.email %></strong>.
  <%= link_to 'Edit profile', edit_user_registration_path, :class => 'navbar-link' %> |
  <%= link_to "Logout", destroy_user_session_path, method: :delete, :class => 'navbar-link'  %>
<% else %>
  <%= link_to "Sign up", new_user_registration_path, :class => 'navbar-link'  %> |
  <%= link_to "Login", new_user_session_path, :class => 'navbar-link'  %>
<% end %>
</p>

Devise

rails g devise:install

Migrations:

#add_providers
class UpdateUsers < ActiveRecord::Migration[5.2]
    def change
      add_column(:users, :provider, :string, limit: 50, null: false, default: '')
      add_column(:users, :uid, :string, limit: 500, null: false, default: '')
    end
end
#create_sessions
class CreateSessions < ActiveRecord::Migration[5.2]
    def change
      create_table :sessions do |t|
        t.string :session_id, null: false
        t.text :data
        t.timestamps
      end

      add_index :sessions, :session_id, unique: true
      add_index :sessions, :updated_at
    end
end
#add_social_fields
class AddSocialToUsers < ActiveRecord::Migration[5.2]
  def change
      change_table :users do |t|
        t.column :name, :string, :default => "None"
    end
  end
end

Setup:

#/config/initializers/devise.rb
config.omniauth :github, ENV['GITHUB_APP_ID'], ENV['GITHUB_APP_SECRET'], scope: 'user,public_repo'
config.omniauth :twitter, ENV['TWITTER_APP_ID'], ENV['TWITTER_APP_SECRET']
config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], scope: 'userinfo.email,userinfo.profile'
config.omniauth :facebook, ENV['FACEBOOK_CLIENT_ID'], ENV['FACEBOOK_CLIENT_SECRET'], scope: 'public_profile,email'

# Find and change these:
config.sign_out_via = :get
config.mailer_sender = '<mailgun email domain>'
config.parent_mailer = 'ActionMailer::Base'
config.reconfirmable = false
#/config/environments/development.rb
config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

config.action_mailer.delivery_method = :smtp

config.action_mailer.smtp_settings = {
  address: 'smtp.mailgun.org',
  # port: 587,
  domain: ENV['MAILER_DOMAIN'],
  authentication: 'plain',
  user_name: ENV['MAILER_USERNAME'],
  password: ENV['MAILER_PASSWORD']
}
#/config/initialisers/session_store.rb
Rails.application.config.session_store :active_record_store, key: '_devise-omniauth_session'

Models:

#/app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :confirmable, :trackable, :omniauthable, omniauth_providers: [:github, :twitter]

     def self.create_from_provider_data(provider_data)
       where(provider: provider_data.provider, uid: provider_data.uid).first_or_create do | user |
         user.email = provider_data.info.email
         user.password = Devise.friendly_token[0, 20]
         user.social_desc = provider_data.info.description
         user.social_image = provider_data.info.image
         user.social_location = provider_data.info.location
         user.social_nickname = provider_data.info.nickname
         user.skip_confirmation!
         user.save
       end
   end
end

Controllers:

rails generate controller users/omniauth
#/config/controllers/user/omniauth_controller.rb
class Users::OmniauthController < Devise::RegistrationsController
    # github callback
    def github
      @user = User.create_from_provider_data(request.env['omniauth.auth'])
      if @user.persisted?

          session[:info] = request.env['omniauth.auth']

        sign_in_and_redirect @user
        set_flash_message(:notice, :success, kind: 'Github') if is_navigational_format?
      else
        flash[:error] = 'There was a problem signing you in through Github. Please register or try signing in later.'
        redirect_to new_user_registration_url
      end
    end

    # twitter callback
    def twitter
      @user = User.create_from_provider_data(request.env['omniauth.auth'])
      if @user.persisted?

        session[:creds] = request.env['omniauth.auth']['extra']['access_token']

        sign_in_and_redirect @user
        set_flash_message(:notice, :success, kind: 'Twitter') if is_navigational_format?
      else
        flash[:error] = 'There was a problem signing you in through Twitter. Please register or try signing in later.'
        redirect_to new_user_registration_url
      end
    end

    # google callback
      def google_oauth2
        @user = User.create_from_provider_data(request.env['omniauth.auth'])
        if @user.persisted?
          sign_in_and_redirect @user
          set_flash_message(:notice, :success, kind: 'Google') if is_navigational_format?
        else
          flash[:error] = 'There was a problem signing you in through Google. Please register or try signing in later.'
          redirect_to new_user_registration_url
        end
      end

      # facebook callback
      def facebook
        @user = User.create_from_provider_data(request.env['omniauth.auth'])
        if @user.persisted?
          sign_in_and_redirect @user
          set_flash_message(:notice, :success, kind: 'Facebook') if is_navigational_format?
        else
          flash[:error] = 'There was a problem signing you in through Facebook. Please register or try signing in later.'
          redirect_to new_user_registration_url
        end
      end



    def failure
      flash[:error] = 'There was a problem signing you in. Please register or try signing in later.'
      redirect_to new_user_registration_url
    end
end

Routes:

devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth' }

Redirecting to Finish Making a Profile

rails generate scaffold Profile display_name:string location:string bio:text user:references
#/models/User
class User < ApplicationRecord
  has_one :profile

  devise :database_authenticatable, :registerable,
       :recoverable, :rememberable, :validatable, :confirmable, :trackable, :omniauthable, omniauth_providers: [:github, :twitter, :google_oauth2, :facebook]

     def self.create_from_provider_data(provider_data)
       where(provider: provider_data.provider, uid: provider_data.uid).first_or_create do | user |
         user.email = provider_data.info.email
         user.password = Devise.friendly_token[0, 20]
         user.name = provider_data.info.name
         user.skip_confirmation!
         user.save
       end
     end
end
#/models/Profile
class Profile < ApplicationRecord
  belongs_to :user
  validates_associated :user
end
#/controllers/profile
# GET /profiles/new
  def new
    # @profile = Profile.new
    @profile = current_user.build_profile
  end

  # GET /profiles/1/edit
  def edit
    @profile = current_user.profile.find(params[:id])
  end

  # POST /profiles
  # POST /profiles.json
  def create
    # @profile = Profile.new(profile_params)
    @profile = current_user.build_profile(profile_params)

    respond_to do |format|
      if @profile.save
        format.html { redirect_to @profile, notice: 'Profile was successfully created.' }
        format.json { render :show, status: :created, location: @profile }
      else
        format.html { render :new }
        format.json { render json: @profile.errors, status: :unprocessable_entity }
      end
    end
  end