As you might have seen normal signup and login is rather hectic.I mean you have to feel the form, rememember your password.The database maybe compromised and password even though hashed can be reverse engineered and this poses a security threat.
Create an app with google¶
It may take a few minute to create, the
Search for Google+API and enable it Search for Contacts API The sidebar on the left should have a Credentials button, click it, and hit Create credentials Before you create the credentials, you will be prompted to create the consent screen. The only mandatory field is the app name, so go ahead and fill that in. Now, you will be able to create your credentials. The application type is Web application. You can name something descriptive. The Authorized redirect URIs should have one URL of the form:
The OAuth2.0 protocol which OmniAuth abstracts, relies on a callback URL to pass the authenticated user object into your application. OmniAuth handles the necessary juggling to get that user object in the first place. The developer should only be responsible for handling that user object, which we will do in a bit. After filling out this form, you will be presented with your Client ID and Client Secret, both of which are used for authorizing your application to use Google’s APIs.
Intergrating with rails app¶
Add it in the Gemfile like:
Create config/initializers/omniauth.rb.As the name suggests, all code inside of initializers is run when the application starts up. This code adds OmniAuth to the Rack middleware. The provider method also accepts an options hash described here.
Add this to config/initializers/omniauth.rb file.
1 2 3
Rails.application.config.middleware.use OmniAuth::Builder do provider :google_oauth2, "GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET" end
GOOGLE_CLIENT_SECRET with the values generated in the Cloud Console.
The auth hash returned contains a ton of information about the User.We will choose provider, uid, email, first_name, last_name.
Let us generate user like so:
rails g model User provider uid email first_name last_name
Migrate like so:
1 2 3 4 5 6 7 8 9 10 11 12
class User < ApplicationRecord def self.find_or_create_from_auth_hash(auth) where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user| user.provider = auth.provider user.uid = auth.uid user.first_name = auth.info.first_name user.last_name = auth.info.last_name user.email = auth.info.email user.save! end end end
The class method
find_or_create_from_auth_hash will use the hash sent by Google to look up our user model in the database. The function
first_or_initialize.tap returns the User object if it finds one, updates the user if any of the information has changed, or creates and saves a new one if the user didn’t exist at all. We can identify a user by the
provider and the
uid . The uid really should be enough to identify the user, but we can avoid the rare collision of more than one provider having the same
Now we are done creating the model, so let’s configure the routes.
The routes provide a nice summary of how everything works together. Add the following code to your
1 2 3 4 5 6 7 8 9 10 11 12
Rails.application.routes.draw do get 'users/login', to: redirect('/auth/google_oauth2'), as: 'login' get 'user/logout', to: 'sessions#destroy', as: 'logout' get 'auth/:provider/callback', to: 'sessions#create' get 'auth/failure', to: redirect('/') get 'home', to: 'home#index' get '/users/show', to: 'users#show', as: 'user' root to: "home#show" end
auth/:provider/callback creates a new session with the User object (turned auth hash) returned by Google. It accepts a parameter :provider in case you had multiple providers. If you decide to include multiple providers, then login must redirect to a generic provider selection menu, rather than straight to Google. In our case,
auth/google_oauth2/callback would have also worked fine.
auth/failure is requested by the provider if the user fails to accept the requested permissions. In our case, we redirect to root if this occurs.
rails g Home show
rails g Users show
1 2 3 4
class HomeController < ApplicationController def show end end
1 2 3 4 5
class UsersController < ApplicationController before_action :authenticate def show end end
before_action tells Rails to run the :authenticate method before any action is called. We will define
1 2 3 4 5 6 7 8 9 10 11 12 13 14
class SessionsController < ApplicationController def create @user = User.find_or_create_from_auth_hash(request.env["omniauth.auth"]) session[:user_id] = @user.id redirect_to :user end def destroy session[:user_id] = nil redirect_to root_path end end
create action is called upon the request
auth/:provider/callback. That means that create receives the auth hash from the provider, or Google in our case. As you can see, we are calling the find_or_create_from_auth_hash class method we wrote earlier. The hash itself is stored in
request.env["omniauth.auth"]. We also put the User ID into the globally accessible session hash. This allows a user to make multiple requests to the app without having to log in. Finally, after the user is logged in, we redirect them to the :me route to view their account specific information.
destroy removes the
:user_id from the session and redirects the user to root. The next authenticated request the user makes will require a login.
We also need to create the authenticate method in app/controllers/application_controller.rb . Since all other controllers are subclasses of ApplicationController, public methods and variables you define here are inherited by them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
class ApplicationController < ActionController::Base protect_from_forgery with: :exception helper_method :current_user def authenticate redirect_to :login unless user_signed_in? end def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] end def user_signed_in? !@current_user.present? end end
<h1> Welcome, please login to continue </h1> <a href="/auth/google_oauth2">Sign in with Google</a>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<p> <strong>First Name:</strong> <%= @currenr_user.first_name %> </p> <p> <strong>Last Name:</strong> <%= @current_user.last_name %> </p> <p> <strong>Email:</strong> <%= @current_user.email %> </p> <p>
Protecting the environmental variable¶
GOOGLE_CLIENT_SECRET and secrets and should not be exposed
to the version control
We can create a config/development_secrets.yml this file should be add to
.gitignore file to keep it our version.
The content of config/development_secrets.yml should be like so:
1 2 3
development: GOOGLE_CLIENT_ID: xxxxxxx GOOGLE_SECRET_KEY: xxxxxxx
For now we are in development environment.And we setup our environment variables likes replace
with appropriate values.
Then we need to tweak our omniauth.rb file to our environmental variables like so:
1 2 3
Rails.application.config.middleware.use OmniAuth::Builder do provider :google_oauth2, Rails.application.secrets.GOOGLE_CLIENT_ID, Rails.application.secrets.GOOGLE_SECRET_KEY end
Then ignore our secrets likes so: