Authenticating users with Ruby

This part of the Bookshelf app tutorial shows how to create a sign-in workflow for users and how to use profile information to provide users with personalized functionality.

By using Google Identity Platform, you can easily access information about your users while ensuring their sign-in credentials are safely managed by Google. Use OAuth 2.0 to provide a sign-in workflow for all users of your app. It provides your app with access to basic profile information about authenticated users.

This page is part of a multi-page tutorial. To start from the beginning and read the setup instructions, go to Ruby Bookshelf app.

Creating a web app client ID

A web app client ID lets your app authorize users and access Google APIs on behalf of your users.

  1. In the Google Cloud Platform Console, go to Credentials.

    Credentials.

  2. Click OAuth consent screen. For the product name, enter Ruby Bookshelf App.

  3. For Authorized Domains, add your App Engine app name as [YOUR_PROJECT_ID].appspot.com. Replace [YOUR_PROJECT_ID] with your GCP project ID.

  4. Fill in any other relevant, optional fields. Click Save.

  5. Click Create credentials > OAuth client ID.

  6. In the Application type drop-down list, click Web Application.

  7. In the Name field, enter Ruby Bookshelf Client.

  8. In the Authorized redirect URIs field, enter the following URLs, one at a time.

    http://localhost:3000/auth/google_oauth2/callback
    http://[YOUR_PROJECT_ID].appspot.com/auth/google_oauth2/callback
    https://[YOUR_PROJECT_ID].appspot.com/auth/google_oauth2/callback

  9. Click Create.

  10. Copy the client ID and client secret and save them for later use.

Installing dependencies

Go to the getting-started-ruby/4-auth directory, and enter this command:

bundle install

Configuring settings

  1. Copy the example settings file.

    cp config/settings.example.yml config/settings.yml
    
  2. Edit the settings.yml file. Replace [YOUR_CLIENT_ID] with your web app client ID and [YOUR_CLIENT_SECRET] with your web app secret.

    default: &default
      oauth2:
        client_id: [YOUR_CLIENT_ID]
        client_secret: [YOUR_CLIENT_SECRET]
    
  3. In the settings.yml file, replace the values for project_id and gcs_bucket with the values you used in the Using Cloud Storage section.

    For example, suppose your web app client ID is XYZCLIENTID, your client secret is XYZCLIENTSECRET, your project name is my-project, and your Cloud Storage bucket name is my-bucket. Then the default section of your settings.yml file would look like this:

    default: &default
      project_id: my-project
      gcs_bucket: my-bucket
      oauth2:
        client_id: XYZCLIENTID
        client_secret: XYZCLIENTSECRET
    
  4. Copy the database.example.yml file.

    cp config/database.example.yml config/database.yml
    
  5. Configure the sample app to use the same database that you set up during the Using structured data portion of this tutorial:

    Cloud SQL

    • Edit database.yml. Uncomment the lines in the Cloud SQL portion of the file.

       mysql_settings: &mysql_settings
         adapter: mysql2
         encoding: utf8
         pool: 5
         timeout: 5000
         username: [MYSQL_USER]
         password: [MYSQL_PASS]
         database: [MYSQL_DATABASE]
         socket: /cloudsql/[YOUR_INSTANCE_CONNECTION_NAME]
      
      • Replace [MYSQL_USER] and [MYSQL_PASS] with your Cloud SQL instance username and password that you created previously.

      • Replace [MYSQL_DATABASE] with the name of the database that you created previously.

      • Replace [YOUR_INSTANCE_CONNECTION_NAME] with the Instance Connection Name of your Cloud SQL instance.

    • Run migrations.

      bundle exec rake db:migrate
      

    PostgreSQL

    • Edit database.yml. Uncomment the lines in the PostgreSQL portion of the file. Replace the your-postgresql-* placeholders with the values for your PostgreSQL instance and database. For example, suppose your IPv4 address is 173.194.230.44, your username is postgres, and your password is pword123. Also suppose your database name of bookshelf. Then the PostgreSQL portion of your database.yml file would look like this:

      # PostgreSQL Sample Database Configuration
      # ----------------------------------------
        adapter: postgresql
        encoding: unicode
        pool: 5
        username: postgres
        password: pword123
        host: 173.194.230.44
        database: bookshelf
      
    • Create the required database and tables.

      bundle exec rake db:create
      bundle exec rake db:migrate
      

    Cloud Datastore

    • Edit database.yml. Uncomment the one line in the Cloud Datastore portion of the file. Replace your-project-id with your Google Cloud Platform project ID. For example, suppose your project ID is my-project: Then the Cloud Datastore portion of your database.yml file would look like this:

      # Google Cloud Datastore Sample Database Configuration
      # ----------------------------------------------------
      dataset_id: my-project
      
    • Run a rake task to copy the sample project files for Cloud Datastore.

      bundle exec rake backend:datastore
      

Running the app on your local machine

  1. Start a local web server.

    bundle exec rails server
    
  2. In your web browser, enter the following address:

    http://localhost:3000

Now you can browse the app's web pages, sign in using your Google account, add books, and see the books you've added using the My Books link in the top navigation bar.

To exit the local web server, press Control+C .

Deploying the app to the App Engine flexible environment

  1. Compile the JavaScript assets for production.

    RAILS_ENV=production bundle exec rake assets:precompile
    
  2. Deploy the sample app.

    gcloud app deploy
    
  3. In your web browser, enter the following address.

    https://[YOUR_PROJECT_ID].appspot.com
    

If you update your app, you can deploy the updated version by entering the same command you used to deploy the app the first time. The new deployment creates a new version of your app and promotes it to the default version. The older versions of your app remain, as do their associated VM instances. Be aware that all of these app versions and VM instances are billable resources.

You can reduce costs by deleting the non-default versions of your app.

To delete an app version:

  1. In the GCP Console, go to the Versions page for App Engine.

    Go to the Versions page

  2. Select the checkbox for the non-default app version you want to delete.
  3. Click Delete to delete the app version.

For complete information about cleaning up billable resources, see the Cleaning up section in the final step of this tutorial.

App structure

The following diagram shows the app's components and how they connect to each other.

Auth sample structure

Understanding the code

This section walks you through the sample code and explains how it works.

User sign-in

The form for the app's home page has a new link so users can sign in.

<%= link_to "Login", login_path %>

The /login route is configured in the config/routes.rb file.

get "/login", to: redirect("/auth/google_oauth2")

When you click Log in, you are redirected to the Google OAuth 2.0 user consent screen. The OmniAuth and Google OAuth2 Ruby gems provide support for the sign-in workflow.

gem "omniauth"
gem "omniauth-google-oauth2"

The omniauth.rb initializer file configures OmniAuth to use Google OAuth 2.0 for user sign-in. The code in the omniauth.rb file reads configuration settings from the oauth2 section of config/settings.yml.

Rails.application.config.middleware.use OmniAuth::Builder do
  config = Rails.application.config.x.settings["oauth2"]

  provider :google_oauth2, config["client_id"],
                           config["client_secret"],
                           image_size: 150
end

When you click Allow on the Google OAuth 2.0 user consent screen, the authorization service issues an HTTP GET request for the sample app's /auth/google_oauth2/callback route, which is configured in the routes.rb file.

get "/auth/google_oauth2/callback", to: "sessions#create"

resource :session, only: [:create, :destroy]

The handler for the callback route is the create method of the sample app's SessionController class. The omniauth.auth request variable provides your profile information, which includes your identifier, display name, and profile image. The create method reads the profile information and stores it in a User object that is serialized into the session.

class SessionsController < ApplicationController

  # Handle Google OAuth 2.0 login callback.
  #
  # GET /auth/google_oauth2/callback
  def create
    user_info = request.env["omniauth.auth"]

    user           = User.new
    user.id        = user_info["uid"]
    user.name      = user_info["info"]["name"]
    user.image_url = user_info["info"]["image"]

    session[:user] = Marshal.dump user

    redirect_to root_path
  end

The app's home page has new elements to display information about the signed-in user.

<% if logged_in? %>
  <% if current_user.image_url %>
    <%= image_tag current_user.image_url, class: "img-circle", width: 24 %>
  <% end %>
  <span>
    <%= current_user.name %> &nbsp;
    <%= link_to "(logout)", logout_path %>
  </span>

The logged_in? and current_user helper methods read the User object in the user's session.

class ApplicationController < ActionController::Base
  helper_method :logged_in?, :current_user

  def logged_in?
    session.has_key? :user
  end

  def current_user
    Marshal.load session[:user] if logged_in?
  end

List a user's books

When a signed-in user adds a new book, their user ID is associated with the book:

def create
  @book = Book.new book_params

  @book.creator_id = current_user.id if logged_in?

  if @book.save
    flash[:success] = "Added Book"
    redirect_to book_path(@book)
  else
    render :new
  end
end

The app's home page has a new Mine link, so signed-in users can view only the books they have added.

<% if logged_in? %>
  <li><%= link_to "Mine", user_books_path %></li>
<% end %>

The index action of the UserBooksController queries for books that were added by the signed-in user.

Cloud SQL / PostgreSQL

@books = Book.where(creator_id: current_user.id).
              limit(PER_PAGE).offset(PER_PAGE * page)

Cloud Datastore

@books, @more = Book.query creator_id: current_user.id,
                           limit: PER_PAGE,
                           cursor: params[:more]
def self.query options = {}
  query = Google::Cloud::Datastore::Query.new
  query.kind "Book"
  query.limit options[:limit]   if options[:limit]
  query.cursor options[:cursor] if options[:cursor]

  if options[:creator_id]
    query.where "creator_id", "=", options[:creator_id]
  end

User sign-out

The form for the app's home page has a new logout link so that users can sign out.

<%= link_to "(logout)", logout_path %>

The logout_path is configured in the config/routes.rb file.

get "/logout", to: "sessions#destroy"

The destroy action of the SessionsController deletes the user from the session.

def destroy
  session.delete :user

  redirect_to root_path
end
Was this page helpful? Let us know how we did:

Send feedback about...