Login with Facebook and phone number- Ruby on Rails 7

Dejan Vujovic
21 min readMay 1, 2023

--

In this tutorial, I will teach you how to create new Ruby on Rails application with tailwind CSS, and how to log in with Fb and phone number as authentication. In the next two tutorials, I will show you how to refactor code using RoR best practices and rebase Github commits.

Why this tutorial is unique. You can’t find anything similar for free on the web and I’ll show you how to create a passwordless application to avoid model validation when needed. Of course, how to use the devise gem for such authentications. It will be interesting to follow.

Before we begin, you must have Ruby and Rails installed on your machine. I’m using Rails 7.0.4 and will be using PostgreSQL as a database.

Step 1:

With the following command, we will create a new rails app with a Postgres database and Tailwind CSS:

rails new login-fb-phone  --css tailwind --database=postgresql

When we have a new RoR application opened in Visual Studio Code, just in the terminal write bin/setup which will create a database, and bin/dev which will open the foreman on localhost:3000.

Now we should open a new repository on GitHub called login-fb-phone.

In your VSC terminal write the command ‘git-init’ and ‘git add .’ From your GitHub repo just take the commands:

git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/DeVuDeveloper/login-fb-phone.git
git push -u origin main

Now reload GitHub and you will have code in your repo. We need to use the best practice so we will use the command in the terminal ‘git flow init’. It will initialize the git flow and make a development branch. Just push it making it upstream. Now make rules for branches and just set as default branch ‘develop’.

In order to better understand the code and the problems that will be encountered along the way, I will use 3 GitHub branches. Let’s create the first one:

git checkout -b login-fb
git push --set-upstream origin login-fb



Now we have a branch where I will show you how to log in via Facebook.

Step 2:

Since we now have everything we need, let’s move on to the next step.

We will only do this for the development environment and I will create root applications first. Create Welcome controller:

We will only do this for the development environment and I will create root applications first.

bin/rails g controller Welcome

This will generate the Welcome controller. The welcome controller should look like this.

class WelcomeController < ApplicationController
def index
end
end

In the app/views/welcome folder, just add an index.html.erb file to look like this.

<h1>This is root</h1>

Also, we will update the file app/views/layout/application.html.erb. We will use link_to ‘Login’ instead of ‘Signup’ because the gem devise will create already sessions for us, which I will explain later:

<!DOCTYPE html>
<html>
<head>
<title>LoginFbPhone</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>

<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>

<body>
<p data-test="notice" class="notice"><%= notice %></p>
<p data-test="alert" class="alert"><%= alert %></p>
<%= link_to "Root", root_path %>
<% if current_user.present? %>
<span data-test="current-user-email"><%= current_user.phone || current_user.email %></span>
<%= button_to "Sign out", destroy_user_session_path, method: :delete, data: { turbo: false }, form_class: "d-inline" %>

<% else %>
<%= link_to "Login", new_user_registration_path %>

<% end %>

<%= yield %>
</body>
</html>

Now in config/routes.db we will create route mapping to the controller and root for our welcome page.

Rails.application.routes.draw do
get "welcome/index"
root "welcome#index"
end

Now just type the commands: bin/setup and bin/dev in the terminal and it will bring up foreman. Now in the browser address type localhost:3000 and you should have this.

This is root

Voila. We have root and we can start.

We can commit changes to our GitHub repo:

git add .
git commit -m 'welcome controller and root'
git push

I strongly recommend this pattern. Convention over configuration (also known as coding by convention) is a software design paradigm used by software frameworks that attempts to decrease the number of decisions that a developer using the framework is required to make without necessarily losing flexibility and don’t repeat yourself (DRY) principles.

So we will not make unnecessary user configuration and authentication, but we will use the existing “gem devise”. In your Gemfile just at the end add
gem “devise”, “~> 4.9” and run command ‘bundle’. Also, this updated gem works fine with Hotwire and StimulusReflex which I will tell you later. Just run:

bundle install
rails g devise:install
rails generate devise user
rails g devise:views

We got a newly generated file in the db/migrate folder. With this migration, we create a User. Since we said at the start that we log in with Facebook and a phone, we don’t need a password because we will authenticate via Facebook and the code that comes with the SMS. That’s why our migration should look like this:

ActiveRecord::Schema[7.0].define(version: 2023_05_05_195230) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "phone"
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "provider"
t.string "uid"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["phone"], name: "index_users_on_phone", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
end

Run bin/rails db:migrate. And then we have devise installed. In the User model, we will assign what we need and continue with the logic.
Navigate to app/models/user.rb file and it should look like this:

class User < ApplicationRecord

devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
end

Step 3:

Login with Facebook. Now that we have devise installed, we need a new gem. In your Gemfile add:

gem 'omniauth-facebook'

Run ‘bundle install’. You have installed oauth2 version 2.0.9, congratulations! Now we can again commit changes to GitHub repo.

git add .
git commit -m 'Install devise and omniauth-gem'
git push

Next, we create a migration to add provider and uid to the user model.

rails g migration AddOmniauthToUsers provider:string uid:string

Run: bin/railsdb:migrate and your app/db/schema.rb should look like this:


class AddOmniauthToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :provider, :string
add_column :users, :uid, :string
end
end

Now we need to update the devise initializer file to include the APP_ID and APP_SECRET we saved it for later. Navigate to config/initializers/devise.rb, and add this:

config.omniauth :facebook, "APP_ID", "APP_SECRET"

We will be professional and use the possibility of Rail's credentials to implement and share credentials. You can use this command to edit it in your code editor. Run this code in your terminal.

EDITOR="code --wait" bin/rails credentials:edit

Change code part to your code editor if you don't use VScode. You will have secret_key_base where you can put the following credentials we need:

facebook:
APP_ID: '<facebook_app_id>'
APP_SECRET: '<facebook_app_secret>'

In order to find these credentials you must log in here:

We need to update the config/initializers/devise.rb file:

config.omniauth :facebook, Rails.application.credentials.facebook[:APP_ID], Rails.application.credentials.facebook[:APP_SECRET], token_params: { parse: :json }

Next, we need to make the user model omniauthable. Add this to the user model:

 devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :trackable, :omniauthable, omniauth_providers: %i[facebook]

It is time to create an omniauth controller in a user folder to respond to Facebook authentication:

bin/rails g controller users/omniauthCallbacksController

This controller should inherit from Devise::OmniauthCallbacksController:


class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
@user = User.from_omniauth(request.env["omniauth.auth"])

if @user.persisted?
sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated
set_flash_message(:notice, :success, kind: "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"].except(:extra) # Removing extra as it can overflow some session stores
redirect_to new_user_registration_url
end
end

def failure
redirect_to root_path
end
end

Next, we go back to the user model to add the from_omniauth self method. This method finds or creates a new user when they register. The name_split part for those that store the first_name and last_name of their users:

  def self.from_omniauth(auth)
find_or_create_by(provider: auth.provider, uid: auth.uid) do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0, 20]
end
end

After this, we will update the routes file to include the new controller we just added:

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

Finally, we add the Facebook button to our view using a Facebook path provided by gem devise. We can use this same path for both registration and login because when we log in, gem devise will make a session, and log out button will work properly too. We just need to update the app/views/devise/registrations/new.html.erb file. I will use an already-made design with Figma, converted to HTML.
I will also add input for logging in with a mobile number for later. Our file should look like this:

<div class="pt-2 bg-gray-800 flex flex-col sm:flex-col md:flex-col 
pl-0 h-screen pb-[152px] gap-[119px] mb-8">

<header class="px-4 mt-4 flex text-white font-medium w-full justify-between">
<div class="h-8 w-[35px]">
<%= link_to root_path do %>

<svg width="100%" height="100%" preserveAspectRatio="none" viewBox="0 0 35 32" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M 14.984 28.438 C 15.179 27.937 15.272 27.402 15.257 26.865 C
15.287 25.376 15.685 23.918 16.415 22.621 C 17.144 21.326 18.183
20.232 19.439 19.438 C 19.904 19.189 20.314 18.847 20.644 18.435
C 20.973 18.023 21.215 17.548 21.356 17.039 C 21.497 16.53
21.532 15.998 21.462 15.475 C 21.391 14.952 21.214 14.449 20.943
13.996 C 20.581 13.391 20.06 12.895 19.439 12.565 C 19.083
12.318 18.741 12.052 18.415 11.767 L 17.598 10.967 C 16.101
9.393 15.262 7.307 15.251 5.135 C 15.267 4.598 15.174 4.063
14.98 3.562 C 14.785 3.061 14.492 2.604 14.118 2.218 C 13.746
1.832 13.3 1.526 12.806 1.317 C 12.313 1.107 11.782 1 11.246 1 C
10.711 1.001 10.181 1.109 9.687 1.318 C 9.194 1.527 8.748 1.833
8.375 2.218 C 8.002 2.605 7.709 3.062 7.515 3.563 C 7.32 4.064
7.228 4.599 7.242 5.136 C 7.251 5.833 7.443 6.516 7.799 7.116 C
8.154 7.715 8.661 8.209 9.268 8.549 C 11.727 10.105 13.438 13.08
13.438 16.006 C 13.432 18.159 12.609 20.23 11.134 21.799 L 9.274
23.451 C 8.667 23.79 8.16 24.284 7.804 24.884 C 7.447 25.483
7.256 26.167 7.247 26.865 C 7.232 27.402 7.324 27.937 7.519
28.438 C 7.714 28.939 8.006 29.396 8.38 29.782 C 8.753 30.167
9.199 30.474 9.692 30.683 C 10.186 30.892 10.716 31 11.252 31 C
11.788 30.999 12.318 30.891 12.812 30.682 C 13.305 30.473 13.751
30.167 14.124 29.782 C 14.498 29.396 14.79 28.938 14.984 28.438
Z" fill="#6366F1" />
<path d="M 20.416 7.36 C 19.975 6.698 19.74 5.919 19.74 5.123 C
19.741 4.057 20.163 3.034 20.915 2.278 C 21.569 1.62 22.431
1.211 23.354 1.119 C 24.277 1.028 25.203 1.26 25.973 1.777 C
26.633 2.22 27.147 2.848 27.449 3.583 C 27.752 4.318 27.831
5.127 27.677 5.907 C 27.522 6.687 27.141 7.405 26.58 7.969 C
26.022 8.532 25.308 8.916 24.53 9.071 C 23.753 9.227 22.946
9.147 22.214 8.842 C 21.482 8.537 20.856 8.021 20.416 7.36 Z" fill="#6366F1" />
<path d="M 21.521 23.518 C 22.18 23.076 22.955 22.84 23.747 22.84
V 22.838 C 24.274 22.839 24.795 22.943 25.281 23.146 C
25.767 23.348 26.208 23.645 26.579 24.018 C 27.235 24.678
27.643 25.544 27.734 26.47 C 27.825 27.396 27.594 28.325
27.079 29.1 C 26.64 29.762 26.014 30.279 25.28 30.584 C
24.548 30.889 23.742 30.969 22.964 30.813 C 22.187 30.658
21.473 30.274 20.914 29.711 C 20.354 29.146 19.972 28.429
19.818 27.649 C 19.663 26.868 19.742 26.06 20.045 25.324 C
20.348 24.589 20.862 23.961 21.521 23.518 Z" fill="#6366F1" />
<path d="M 33.324 13.758 C 33.764 14.42 33.999 15.197 33.999
15.993 C 33.999 17.06 33.576 18.083 32.824 18.839 C 32.453
19.212 32.012 19.509 31.526 19.711 C 31.04 19.914 30.519
20.018 29.992 20.019 C 29.199 20.019 28.425 19.783 27.766
19.341 C 27.107 18.898 26.593 18.27 26.29 17.535 C 25.987
16.799 25.908 15.991 26.063 15.21 C 26.217 14.43 26.599
13.713 27.159 13.148 C 27.718 12.585 28.432 12.201 29.21
12.046 C 29.988 11.89 30.794 11.97 31.526 12.275 C 32.259
12.58 32.885 13.097 33.324 13.758 Z" fill="#6366F1" />
<path d="M 2.78 12.647 C 3.439 12.205 4.213 11.969 5.006
11.969 C 5.533 11.97 6.054 12.075 6.539 12.278 C 7.025
12.48 7.466 12.777 7.837 13.15 C 8.493 13.81 8.901
14.675 8.992 15.601 C 9.083 16.527 8.852 17.455 8.337
18.23 C 7.898 18.891 7.272 19.408 6.539 19.713 C 5.807
20.016 5.001 20.095 4.224 19.94 C 3.446 19.785 2.733
19.402 2.173 18.84 C 1.613 18.276 1.232 17.558 1.077
16.778 C 0.922 15.998 1.002 15.19 1.304 14.454 C 1.607
13.719 2.12 13.09 2.78 12.647 Z" fill="#6366F1" />
</svg>
<% end %>
</div>
</header>


<div class="my-0 md:my-5 max-w-full flex flex-wrap px-4">

<div class="flex flex-col md:w-1/2 lg:w-1/2
text-4xl md:text-5xl justify-between">
<div class=" mb-10 leading-none relative h-20
font-extrabold md:mb-16">
<p class=" tracking-tight leading-10
text-white inline m-0">
Login with Fb
<br />
</p>
<p class="tracking-tight leading-10
text-indigo-400 inline m-0 mb-12">
and phone number.
</p>
</div>
<div>
<p class="text-xl md:text-2xl font-normal leading-7
text-gray-300 h-56 pt-4">
This is totorial on how to log in with Facebook and your phone number in Ruby on Rails 7 web application.
</p>
</div>
</div>


<div class="flex justify-center items-center md:w-1/2 lg:w-1/2 p-0 m-0
">
<div class="bg-white flex flex-col pt-0 pl-0 rounded:md
h-[368px]">
<div class="px-4 py-8 bg-white gap-6 flex flex-col
rounded:md">
<div class="gap-1 flex flex-col self-stretch pt-0
pl-0 text-gray-900 text-left font-medium">
<p class="text-sm leading-5 h-5 ">Log
in</p>
<div class="flex justify-center items-center
">
<div class="bg-white flex justify-center
items-center flex-grow rounded-md
pt-[9px] pb-[9px] h-[38px]
drop-shadow-lg overflow-clip
[box-shadow:0px_0px_0px_1px_rgba(209,_213,_219,_1)_inset]
[box-shadow-width:1px]">
<div class="w-5 h-5">

<%= button_to user_facebook_omniauth_authorize_path, data: { turbo: false } do %>
<svg width="100%" height="100%" preserveAspectRatio="none" viewBox="0
0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_5_202)">
<path fillRule="evenodd" clipRule="evenodd" d="M 20 10 C 20 4.477 15.523 0 10
0 C 4.477 0 0 4.477 0 10 C 0
14.991 3.657 19.128 8.438 19.878 V
12.891 H 5.898 V 10 H 8.438 V
7.797 C 8.438 5.291 9.93 3.907
12.215 3.907 C 13.309 3.907 14.453
4.102 14.453 4.102 V 6.562 H
13.193 C 11.95 6.562 11.563 7.333
11.563 8.124 V 10 H 14.336 L
13.893 12.89 H 11.563 V 19.878 C
16.343 19.128 20 14.991 20 10 Z" fill="#6B7280" />
</g>
<defs>
<clipPath id="clip0_5_202">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
<% end %>
</div>
</div>
</div>
</div>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render "devise/shared/error_messages" , resource: resource %>
<div class="gap-2 flex items-center
pl-0 text-gray-500
text-center font-normal ">
<div class="flex-1 h-px bg-gray-300
"></div>
<p class="text-sm leading-5 h-5
w-2.5">or</p>
<div class="flex-1 h-px bg-gray-300
"></div>
</div>
<div>
<%= f.text_field :phone, autofocus: true, autocomplete: "phone" , placeholder: "My Number" , class: 'bg-white flex items-center
rounded-md
text-gray-500 text-left
font-normal pt-[9px] pb-[9px]
pl-[13px]
w-full
drop-shadow-lg overflow-clip
[box-shadow:0px_0px_0px_1px_rgba(209,_213,_219,_1)_inset]
[box-shadow-width:1px]' %>
</div>
<div class="mt-3 bg-indigo-600 flex
justify-center items-center
self-stretch rounded-md text-white
text-left font-medium
pt-[9px] pb-[9px]
drop-shadow-lg overflow-clip">
<%= f.submit "Log in" , class: "text-sm leading-5 h-5
" %>
</div>
</div>
<div class="h-0.5 bg-gray-200"> </div>

<% end %>
<div class="px-4 py-6 bg-gray-50
flex justify-center items-center
text-gray-500
text-left ">
<div class="h-5 leading-none
relative ">
<p class="text-xs font-normal
leading-5 inline m-0">
By clicking on Log in,
</p>
<p class="text-xs font-bold
leading-5 inline m-0">
you accept
</p>
<p class="text-xs font-normal
leading-5 inline m-0">
the therms of use.
</p>
</div>
</div>
</div>
</div>
</div>
</div>

In your browser it should look like this:

We will just add a few things to be sure we can log in with Facebook properly:

Updating file app/controllers/application/controller.rb with permitted parameters:

class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
protect_from_forgery

helper_method :resource

protected

def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up) do |u|
u.permit(:email, :phone, :password, :password_confirmation, :uid, :provider)
end
devise_parameter_sanitizer.permit(:sign_in) do |u|
u.permit(:email, :phone, :password, :password_confirmation, :uid, :provider)
end
devise_parameter_sanitizer.permit(:account_update) do |u|
u.permit(:email, :phone, :password, :password_confirmation, :uid, :provider)
end
end
end

Adding gem ‘omniauth-rails_csrf_protection’ in your Gemfile and run ‘bundle install’. Be sure you have fb credentials stored in Rails encrypted file. Now you can click on the login link from the root, and click on FB icon. You will be redirected to the FB login page:

Just put your fb credentials in and click on Log In button, and you will be redirected to the app root. You can add and commit your code now to the GitHub repo.

Awesome, we just made a login with FB. Now I will show you how to log in with a phone number too. Take a little break, and we continue with coding!

Step 4:

We can make a new branch for the next feature, I named it ‘login-phone’. We already have the form in devise/registration/new.html.erb file. This will be passwordless login, and we will authenticate a user with a verification code sent through SMS. I mentioned before we will avoid Signup, and use only the Login page because already we have the same for Fb login. In this case when we signup(login) session is made and we can successfully sign out. Same we will make with phone number login. We will have login with a verification code, instead password, and sign-out will work as well. On sign-out, we will delete the user and log in again with a new verification code received via SMS. This can be done in another way for passwordless login, with login-token for authentication. I prefer this way.

As I mentioned before this will be login with a phone-number and passwordless. Let’s do it first. In our user model, we will create validation for logging in with a phone and make email and password optional:

  validates :phone, presence: true
validates :phone, uniqueness: {case_sensitive: false}
validates :phone, format: {with: ::Constants::PHONE_REGEX}

def email_required?
false
end

def email_changed?
false
end

def will_save_change_to_email?
false
end

protected

def password_required?
false
end
def password_confirmation_required?
false
end

In the config/initializers/devise.rb file we change from email to phone here:

config.case_insensitive_keys = [:phone]
config.strip_whitespace_keys = [:phone]
config.authentication_keys = [:phone]

Next, we will add columns ‘pin’, ‘pin_sent_at’, and ‘verified’ to the ‘users’ table:

rails generate migration add_verified_to_users pin:integer pin_sent_at:datetime verified:boolean

Created migration should look like this. Notice we set default ‘false’ for column ‘verified’:

class AddVerifiedToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :pin, :integer
add_column :users, :pin_sent_at, :datetime
add_column :users, :verified, :boolean, default: false
end
end

Run ‘rails db:migrate’. Now that we have our data fields, we will update the application_controller.rb file, to be sure the user will be redirected if is not verified:

  before_action :redirect_if_unverified

def redirect_if_unverified
return unless signed_in? && !current_user.verified?
redirect_to verify_path, notice: 'Please verify your phone number'
end

We have to update our routes.rb file:

get '/verify' => 'verify#edit', as: 'verify'
get '/verify' => 'verify#new', as: 'new_verify'
put '/verify' => 'verify#update', as: 'update_verify'
post '/verify' => 'verify#create', as: 'resend_verify'

The idea to solve this task is to create a new pin when we save the user in the database and send the pin through SMS. Later we compare arrived pin with a pin in the database and if they are the same, we update the user’s ‘verified’ column to true. In that case, the user will be logged in and redirected to the root of the application. We need new functionalities for this. First, let’s update our user model:

after_create :send_pin!

def reset_pin!
update_column(:pin, rand(1000..9999))
end

def unverify!
update_column(:verified, false)
end

def send_pin!
reset_pin!
unverify!
self.perform(self)
end

As we see we have a new method ‘send_pin’, responsible for creating or resetting 4-digits pins, unverified users, and sending messages via sms(perform). We will use gem ‘nexmo’ for sending SMS and create the method ‘perform’. Add this gem to your Gemfile and run ‘bundle install’. Please go to nexmo.com( it is changed to Vonage) sign up and get the ‘api_key’ and ‘api_secret’ there. Now we will put credentials in the code, but in the next chapter, we will refactor all code and put credentials in a safe place. Now we can create the ‘perform’ method in the user model:

 def perform(user)
nexmo = Nexmo::Client.new(api_key: 'your api_key', api_secret: 'your api-secret')
resp = nexmo.sms.send( from: "Our application", to: user.phone, text: user.pin.to_s)
user.touch(:pin_sent_at)
end

We will need now a controller for the behavior of verification. Let’s create a new controller for how we can respect the single responsibility principle:


class VerifyController < ApplicationController
skip_before_action :redirect_if_unverified


def new
@user= current_user
end

def create
current_user.send_pin!
redirect_to verify_url, notice: 'Verification code has ben sent to your mobile number.'
end

def edit
@user= current_user
end


def update
@user= current_user
if Time.now > current_user.pin_sent_at.advance(minutes: 60)
flash.now[:alert] = 'Your pin has expired. Please request another.'
render :new and return
elsif params[:user][:pin].try(:to_i) == current_user.pin
current_user.update_attribute(:verified, true)
redirect_to root_url, notice: 'Your phone number has been verified!'
else
flash.now[:alert] = 'The code you entered is invalid.'
render :edit
end
end
end

Here we create pins and send SMS to users. Also if we don't type the code in 60 minutes it is expired. We can resend the code too. When we have a pin from SMS, we compare it with the pin from the database, easy-peasy. We need a template for updating user verification. Create an edit.html.erb file in the views/verify folder. I will use my Figma design again and it should look like this:

<div class="pt-2 bg-gray-800 flex flex-col sm:flex-col md:flex-col lg:flex-row
xl:flex-row pl-0 h-screen gap-[119px] ">

<header class="px-4 mt-4 flex text-white font-medium w-full justify-between">
<div class="h-8 w-[35px]">

<svg width="100%" height="100%" preserveAspectRatio="none" viewBox="0 0 35 32" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M 14.984 28.438 C 15.179 27.937 15.272 27.402 15.257 26.865 C
15.287 25.376 15.685 23.918 16.415 22.621 C 17.144 21.326 18.183
20.232 19.439 19.438 C 19.904 19.189 20.314 18.847 20.644 18.435
C 20.973 18.023 21.215 17.548 21.356 17.039 C 21.497 16.53
21.532 15.998 21.462 15.475 C 21.391 14.952 21.214 14.449 20.943
13.996 C 20.581 13.391 20.06 12.895 19.439 12.565 C 19.083
12.318 18.741 12.052 18.415 11.767 L 17.598 10.967 C 16.101
9.393 15.262 7.307 15.251 5.135 C 15.267 4.598 15.174 4.063
14.98 3.562 C 14.785 3.061 14.492 2.604 14.118 2.218 C 13.746
1.832 13.3 1.526 12.806 1.317 C 12.313 1.107 11.782 1 11.246 1 C
10.711 1.001 10.181 1.109 9.687 1.318 C 9.194 1.527 8.748 1.833
8.375 2.218 C 8.002 2.605 7.709 3.062 7.515 3.563 C 7.32 4.064
7.228 4.599 7.242 5.136 C 7.251 5.833 7.443 6.516 7.799 7.116 C
8.154 7.715 8.661 8.209 9.268 8.549 C 11.727 10.105 13.438 13.08
13.438 16.006 C 13.432 18.159 12.609 20.23 11.134 21.799 L 9.274
23.451 C 8.667 23.79 8.16 24.284 7.804 24.884 C 7.447 25.483
7.256 26.167 7.247 26.865 C 7.232 27.402 7.324 27.937 7.519
28.438 C 7.714 28.939 8.006 29.396 8.38 29.782 C 8.753 30.167
9.199 30.474 9.692 30.683 C 10.186 30.892 10.716 31 11.252 31 C
11.788 30.999 12.318 30.891 12.812 30.682 C 13.305 30.473 13.751
30.167 14.124 29.782 C 14.498 29.396 14.79 28.938 14.984 28.438
Z" fill="#6366F1" />
<path d="M 20.416 7.36 C 19.975 6.698 19.74 5.919 19.74 5.123 C
19.741 4.057 20.163 3.034 20.915 2.278 C 21.569 1.62 22.431
1.211 23.354 1.119 C 24.277 1.028 25.203 1.26 25.973 1.777 C
26.633 2.22 27.147 2.848 27.449 3.583 C 27.752 4.318 27.831
5.127 27.677 5.907 C 27.522 6.687 27.141 7.405 26.58 7.969 C
26.022 8.532 25.308 8.916 24.53 9.071 C 23.753 9.227 22.946
9.147 22.214 8.842 C 21.482 8.537 20.856 8.021 20.416 7.36 Z" fill="#6366F1" />
<path d="M 21.521 23.518 C 22.18 23.076 22.955 22.84 23.747 22.84
V 22.838 C 24.274 22.839 24.795 22.943 25.281 23.146 C
25.767 23.348 26.208 23.645 26.579 24.018 C 27.235 24.678
27.643 25.544 27.734 26.47 C 27.825 27.396 27.594 28.325
27.079 29.1 C 26.64 29.762 26.014 30.279 25.28 30.584 C
24.548 30.889 23.742 30.969 22.964 30.813 C 22.187 30.658
21.473 30.274 20.914 29.711 C 20.354 29.146 19.972 28.429
19.818 27.649 C 19.663 26.868 19.742 26.06 20.045 25.324 C
20.348 24.589 20.862 23.961 21.521 23.518 Z" fill="#6366F1" />
<path d="M 33.324 13.758 C 33.764 14.42 33.999 15.197 33.999
15.993 C 33.999 17.06 33.576 18.083 32.824 18.839 C 32.453
19.212 32.012 19.509 31.526 19.711 C 31.04 19.914 30.519
20.018 29.992 20.019 C 29.199 20.019 28.425 19.783 27.766
19.341 C 27.107 18.898 26.593 18.27 26.29 17.535 C 25.987
16.799 25.908 15.991 26.063 15.21 C 26.217 14.43 26.599
13.713 27.159 13.148 C 27.718 12.585 28.432 12.201 29.21
12.046 C 29.988 11.89 30.794 11.97 31.526 12.275 C 32.259
12.58 32.885 13.097 33.324 13.758 Z" fill="#6366F1" />
<path d="M 2.78 12.647 C 3.439 12.205 4.213 11.969 5.006
11.969 C 5.533 11.97 6.054 12.075 6.539 12.278 C 7.025
12.48 7.466 12.777 7.837 13.15 C 8.493 13.81 8.901
14.675 8.992 15.601 C 9.083 16.527 8.852 17.455 8.337
18.23 C 7.898 18.891 7.272 19.408 6.539 19.713 C 5.807
20.016 5.001 20.095 4.224 19.94 C 3.446 19.785 2.733
19.402 2.173 18.84 C 1.613 18.276 1.232 17.558 1.077
16.778 C 0.922 15.998 1.002 15.19 1.304 14.454 C 1.607
13.719 2.12 13.09 2.78 12.647 Z" fill="#6366F1" />
</svg>

</div>

</header>


<div class="m-0 p-0 md:my-5 w-full flex flex-col md:flex-row md:justify-evenly md:items-end px-4">

<div class=" flex flex-col md:w-1/2 lg:w-1/2
h-[316px] text-4xl md:text-5xl">
<div class=" leading-none relative h-20
font-extrabold md:mb-16">
<p class=" tracking-tight leading-10
text-white inline m-0">
Razmeni mesto
<br />
</p>
<p class="tracking-tight leading-10
text-indigo-400 inline m-0">
za vrtić online
</p>
</div>
<p class="text-xl md:text-2xl font-normal leading-7
text-gray-300 h-56">
Ova platforma omogućava roditeljima u Novom
Sadu da se
povežu i razmene mesta u vrtiću preko online
platforme. To
znači da roditelji koji traže mesto u vrtiću
mogu pronaći
druge roditelje koji su zainteresovani za
razmenu mesta sa
svojom decom.
</p>
</div>



<div class="bg-white flex flex-col items-start text-gray-900 font-medium">
<div class="px-4 w-full bg-white gap-6 flex flex-col items-start self-stretch">
<div class="w-full gap-1 flex flex-col items-start self-stretch">
<p class="text-sm leading-5 m-0">Verifikacija broja</p>
</div>
<%= simple_form_for @user, url: verify_path, method: :put, data: { turbo: false } do |f| %>
<div class='w-full bg-white inline-flex items-center self-stretch rounded-md text-gray-500 text-left font-normal pl-[13px] pr-[13px] pt-[9px] pb-[9pxview-source:http://localhost:3000/users/sign_up] drop-shadow-lg overflow-clip font-[Inter] [box-shadow:0px_0px_0px_1px_rgba(209,_213,_219,_1)_inset] [box-shadow-width:1px]'>

<%= f.text_field :pin, autofocus: true, autocomplete: "pin", value: "", label: false, placeholder: "Verifikacioni kod", class: "text-base leading-6 m-0" %>
</div>
<div class='w-full bg-indigo-600 inline-flex justify-center items-center self-stretch rounded-md text-white text-left font-medium pl-[17px] pr-[17px] pt-[9px] pb-[9px] drop-shadow-lg overflow-clip font-[Inter]'>
<%= f.submit "Verifikuj nalog", class: "text-sm leading-5 m-0"%>
</div>
<% end %>

<%= simple_form_for @user, url: resend_verify_path, method: :post do |f| %>
<% if @user.errors.any? %>
<%= @user.errors.full_messages.to_sentence.capitalize %>
<% end %>

<div class='w-full bg-indigo-600 inline-flex justify-center items-center self-stretch rounded-md text-white text-left font-xs pl-[17px] pr-[17px] pt-[9px] pb-[9px] drop-shadow-lg overflow-clip font-[Inter]'>
<%= f.submit "Resend code", class: "text-sm leading-5 m-0"%>
</div>
<% end %>

</div>
<div class="w-full h-0.5 bg-gray-200"></div>
</div>
</div>
</div>

Now, we need a gem ’simple form’. Just add it to your Gemfile and run ‘bundle install’ and ‘rails generate simple_form:install’.

Our verification template should look like this in your browser:

Let’s check it now. Clear your database, so we are sure we don’t have any Users. From the application root click on the link ‘Login’. Now enter a valid number. We have phone number format as validation in our user model, so if it doesn't allow you to enter your number just change the format. It works fine for me. Just enter the international prefix. Now, as the user is created, the pin is created at the same time and stored in a database. Also, the pin is sent to the Vonage provider and back as an SMS text to you. Soon, you will receive an SMS with a pin. Enter a pin in the verification input and you will be logged and redirected to the root of the application.

But if we sign out and we want to log in again with the same number, we will have an error: ‘Phone has already been taken’. This is because we want to use only the ‘login’ page and it is our ‘sign up’ page in fact. I was talking before about it. As passwordless login, we could use a login token. But maybe we can keep it this way. How? We want to log in with a phone number and authenticate it with the code received with SMS, right? Then we can delete the complete user from the database by clicking on the ‘Sign Out’ button. And the next time we want to log in, just we will receive an again SMS, and log in with the code. So we can keep just the ‘Login’ page. Let’s code it. We will just create a new SessionController which inherits from Devise::SessionController and in destroy session just delete the current user:

class SessionsController < Devise::SessionsController
def destroy
session[:user_id] = nil
current_user.delete
redirect_to root_path
end
end

For this, we need to update our routes.rb file and everything works fine now:

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

We are almost done. Log in with the phone is working properly now. Let’s check the login with Fb we made earlier. It doesn't work. If we watch our code we can see when we create a user it will ask for phone verification, Also we have now a new user’s column ‘verified’ set to default false. But we do not need it for the Fb login. What we can do? Just avoid verification and validation when we log in with Facebook. As we have authentication for Fb login with uid. Let’s create a method in our user model when we can update the user and set ‘verified’ to true if we log in with Fb:

 after_create :update_user_verified_column_to_true
after_create :send_pin!, unless: Proc.new { self.provider == "facebook" }

def update_user_verified_column_to_true
return unless phone.blank?
update_column(:verified, true)
end

Also, we will just avoid phone validation if we log in with Fb. Update our user model with this:

  validates :phone, presence: true, unless: :skip_validation?
validates :phone, uniqueness: {case_sensitive: false}, unless: :skip_validation?
validates :phone, :format => { :with => /[0-9]{3}[- ]?[0-9]{3}[- ]?[0-9]{2}[- ]?[0-9]{2}/ }, unless: :skip_validation?


def skip_validation?
return if provider.blank?
self.save(validate: false)
end

Amazing !!! Everything works awesome now. Congratulation. You just finished this tutorial. If something went wrong, you can find the GitHub repo here. Next time we will refactor this code using Ruby on Rails best practices. You can find the second part of this tutorial here, and the third part here.

--

--