Ruby on Rails — Fetch Google Calendar info(OAuth 2.0)

Photo by Estée Janssens on Unsplash

Making a Ruby on Rails app that fetches users Calendar List.

Before starting

This article assumes that you already created a project on Google Cloud Console and that you already have a CLIENT_ID and a SECRET_KEY from your OAuth client. And that also you have enabled the Calendar API.

P.S. This won’t be a pretty page, it’s just to show you how it works so you can better understand. It’s not for production.

Starting

Lets create a Rails app

rails new glogin — database=mysql — skip-turbolinks — skip-test — skip-coffee — skip-action-cable

Run database creation

rake db:create

Create a home controller

rails g controller home index

Create an events controller

rails g controller events index

Create a User model, so that we have a user.

rails g model User name:string email:string picture_url:string

Create a Token model. In the future we want the user to have many providers that he can use as a login. It’s better to separate token fields out of user and into it’s own model. The provider column will tell us if it’s google or twitter for example.

rails g model Token user_id:integer access_token:string refresh_token:string expires_at:integer provider:string

Add the following gems. Omniauth will be used for login. Api Client for accessing the Calendar API.

gem 'google-api-client', require: 'google/apis/calendar_v3'
gem 'omniauth-google-oauth2'

Create config/initializers/omniauth.rb with content:

Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2,
ENV['G_CLIENT_ID'],
ENV['G_CLIENT_SECRET'],
{ scope: "userinfo.email, calendar" }
end

Routes file like:

# Routes for Google authentication
get 'auth/:provider/callback', to: 'sessions#google_login'
get 'auth/failure', to: redirect('/')
# /auth/google_oauth2 is our google login starter point
get 'login', to: redirect('/auth/google_oauth2')
get 'logout', to: 'sessions#logout'
# Events page
get 'events/index'
# root
root 'home#index'

Application controller like this:

class ApplicationController < ActionController::Base
def log_in(user)
session[:user_id] = user.id
end
def log_out
session[:user_id] = nil
end
helper_method :current_user
def current_user
@current_user ||=
if session[:user_id].present?
User.find_by(id: session[:user_id])
else
nil
end
end
def authenticate
redirect_to(root_path) if current_user.nil?
end
end

The log in and log out just set the user id to the session or in case of log out, removes it. The helper method fetches the user, so its available across all controllers. And authenticate redirects if the user is not logged in.

Session controller like this below. Now this may seem big, but its actually pretty straightforward. You receive the auth info from the login callback from Google. That auth info contains the users name, email and of course the most important thing, the access token and the refresh token. Do note that the refresh token will be send just the first time. It will not be present the second, third, etc time. Its a one time, as you use it to get a new access token, which has an expiration of 5min.

class SessionsController < ApplicationController

def google_login
# Get access tokens from what google returned
auth = request.env["omniauth.auth"]
# Find user with auth info
user = User.from_omniauth(auth)
log_in(user) # Find token
token = user.tokens.find_or_initialize_by(provider: 'google')
# Access_token is used to authenticate requests
token.access_token = auth.credentials.token
token.expires_at = auth.credentials.expires_at
# Refresh_token to request new access_token
# Note: Refresh_token is only sent once during the first request
refresh_token = auth.credentials.refresh_token
token.refresh_token = refresh_token if refresh_token.present?
# save token
token.save
# back to root
redirect_to root_path
end
def logout
log_out
# back to root
redirect_to root_path
end
end

Events controller. Here we fetch calendar data. Ruby has this weird library thats in Alpha state, and its been there for a long time. But this is how somebody would fetch the calendar of a user using their access token. Do note that in case the access token is expired you have to request a new one with the refresh token, otherwise the call will be unsuccessful.

require 'google/api_client/client_secrets.rb'
require 'google/apis/calendar_v3'
class EventsController < ApplicationController
before_action :authenticate
def index
token = current_user.google_token
# Initialize Google Calendar API
service = Google::Apis::CalendarV3::CalendarService.new
# Use google keys to authorize
service.authorization = token.google_secret.to_authorization
# Request for a new access token just incase it expired
if token.expired?
new_access_token = service.authorization.refresh!
token.access_token = new_access_token['access_token']
token.expires_at =
Time.now.to_i + new_access_token['expires_in'].to_i
token.save
end
# Get a list of calendars
@calendar_list = service.list_calendar_lists.items
end
end

User model

class User < ApplicationRecord
has_many :tokens, dependent: :destroy
def self.from_omniauth(auth)
user = where(email: auth.info.email).first_or_initialize do |record|
record.name = auth.info.name
record.email = auth.info.email
end
user.new_record? && user.save
user
end
def google_token
tokens.find_by(provider: 'google')
end
end

Token model

class Token < ApplicationRecord
belongs_to :user
def google_secret
Google::APIClient::ClientSecrets.new(
{ "web" =>
{ "access_token" => access_token,
"refresh_token" => refresh_token,
"client_id" => ENV['G_CLIENT_ID'],
"client_secret" => ENV['G_CLIENT_SECRET'],
}
}
)
end
def expired?
expires_at <= Time.now.to_i
end
end

After Start

Now all you have to do is to put a login link on home#index. If current_user exists you can instead show him the log out link and a link to events page. On the events page just iterate through the calendar_list variable. That’s it.

developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store