-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement the server-side OAuth 2.0 flow
- Loading branch information
1 parent
cb62598
commit 136713d
Showing
15 changed files
with
168 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
app/controllers/google_sign_in/authorizations_controller.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
require 'securerandom' | ||
|
||
class GoogleSignIn::AuthorizationsController < GoogleSignIn::BaseController | ||
def create | ||
redirect_to login_url(scope: 'openid profile email', state: state), | ||
flash: { proceed_to: params.require(:proceed_to), state: state } | ||
end | ||
|
||
private | ||
def login_url(**params) | ||
client.auth_code.authorize_url(prompt: 'login', **params) | ||
end | ||
|
||
def state | ||
@state ||= SecureRandom.base64(64) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
require 'oauth2' | ||
|
||
class GoogleSignIn::BaseController < ActionController::Base | ||
protect_from_forgery with: :exception | ||
|
||
private | ||
def client | ||
@client ||= OAuth2::Client.new \ | ||
GoogleSignIn.client_id, | ||
GoogleSignIn.client_secret, | ||
authorize_url: 'https://accounts.google.com/o/oauth2/auth', | ||
token_url: 'https://www.googleapis.com/oauth2/v3/token', | ||
redirect_uri: callback_url | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
require_dependency 'google_sign_in/redirect_protector' | ||
|
||
class GoogleSignIn::CallbacksController < GoogleSignIn::BaseController | ||
def show | ||
if valid_request? | ||
redirect_to proceed_to_url, flash: { google_sign_in_token: id_token } | ||
else | ||
head :unprocessable_entity | ||
end | ||
end | ||
|
||
private | ||
def valid_request? | ||
flash[:state].present? && params.require(:state) == flash[:state] | ||
end | ||
|
||
def proceed_to_url | ||
flash[:proceed_to].tap { |url| GoogleSignIn::RedirectProtector.ensure_same_origin(url, request.url) } | ||
end | ||
|
||
def id_token | ||
client.auth_code.get_token(params.require(:code))['id_token'] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
GoogleSignIn::Engine.routes.draw do | ||
resource :authorization, only: :create | ||
resource :callback, only: :show | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,10 @@ | ||
require 'active_support' | ||
require 'active_support/rails' | ||
|
||
module GoogleSignIn | ||
mattr_accessor :client_id | ||
mattr_accessor :client_secret | ||
end | ||
|
||
require 'google_sign_in/identity' | ||
require 'google_sign_in/engine' if defined?(Rails) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,7 @@ | ||
require 'google_sign_in/identity' | ||
|
||
module GoogleSignIn | ||
module Helper | ||
def google_sign_in(**form_options, &block) | ||
content_for :head, | ||
google_sign_in_javacript_include_tag + | ||
google_sign_in_client_id_meta_tag + | ||
turbolinks_reload_meta_tag | ||
|
||
google_sign_in_javascript_tag + | ||
google_sign_in_hidden_form_tag(**form_options) + | ||
google_sign_in_click_handler(&block) | ||
def google_sign_in_button(text = nil, proceed_to:, **options, &block) | ||
button_to text, google_sign_in.authorization_path(proceed_to: proceed_to), **options, &block | ||
end | ||
|
||
private | ||
def google_sign_in_javacript_include_tag | ||
javascript_include_tag "https://apis.google.com/js/api.js", async: true, defer: true, | ||
onload: "this.onload=function(){};setupGoogleSignIn()", | ||
onreadystatechange: "if (this.readyState === 'complete') this.onload()" | ||
end | ||
|
||
def google_sign_in_client_id_meta_tag | ||
tag.meta name: "google-signin-client_id", content: GoogleSignIn::Identity.client_id | ||
end | ||
|
||
def turbolinks_reload_meta_tag | ||
tag.meta name: "turbolinks-visit-control", content: "reload" | ||
end | ||
|
||
def google_sign_in_hidden_form_tag(**options) | ||
options.reverse_merge!(html: { style: "display: none" }) | ||
|
||
form_with(**options) do |form| | ||
form.hidden_field(:google_id_token, id: "google_sign_in_token") + form.submit(id: "google_sign_in_submit") | ||
end | ||
end | ||
|
||
def google_sign_in_click_handler(&block) | ||
tag.div(id: "google_sign_in_container", style: "visibility: hidden") { capture(&block) } | ||
end | ||
|
||
def google_sign_in_javascript_tag | ||
javascript_tag <<-JS.strip_heredoc | ||
(function() { | ||
function installAuthClient(callback) { | ||
gapi.load("client:auth2", function() { | ||
gapi.auth2.init().then(callback) | ||
}) | ||
} | ||
function installClickHandler() { | ||
var element = document.getElementById("google_sign_in_container") | ||
var options = new gapi.auth2.SigninOptionsBuilder() | ||
options.setPrompt("select_account") | ||
gapi.auth2.getAuthInstance().attachClickHandler(element, options, handleSignIn) | ||
element.style.visibility = "visible" | ||
} | ||
function handleSignIn(googleUser) { | ||
var token = googleUser.getAuthResponse().id_token | ||
if (token) { | ||
document.getElementById("google_sign_in_token").value = token | ||
document.getElementById("google_sign_in_submit").click() | ||
gapi.auth2.getAuthInstance().signOut() | ||
} | ||
} | ||
window.setupGoogleSignIn = function() { | ||
installAuthClient(installClickHandler) | ||
} | ||
})() | ||
JS | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
require 'uri' | ||
|
||
module GoogleSignIn | ||
module RedirectProtector | ||
extend self | ||
|
||
class Violation < StandardError; end | ||
|
||
QUALIFIED_URL_PATTERN = /\A#{URI::DEFAULT_PARSER.make_regexp}\z/ | ||
|
||
def ensure_same_origin(target, source) | ||
if target =~ QUALIFIED_URL_PATTERN && origin_of(target) != origin_of(source) | ||
raise Violation, "Redirect target #{target} does not have same origin as request (#{source})" | ||
end | ||
end | ||
|
||
private | ||
def origin_of(url) | ||
uri = URI(url) | ||
"#{uri.scheme}://#{uri.host}:#{uri.port}" | ||
rescue ArgumentError | ||
nil | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
require 'test_helper' | ||
require 'google_sign_in/redirect_protector' | ||
|
||
class GoogleSignIn::RedirectProtectorTest < ActiveSupport::TestCase | ||
test "disallows URL target with different host than source" do | ||
assert_raises GoogleSignIn::RedirectProtector::Violation do | ||
GoogleSignIn::RedirectProtector.ensure_same_origin 'https://malicious.example.com', 'https://basecamp.com' | ||
end | ||
end | ||
|
||
test "disallows URL target with different port than source" do | ||
assert_raises GoogleSignIn::RedirectProtector::Violation do | ||
GoogleSignIn::RedirectProtector.ensure_same_origin 'https://basecamp.com:10443', 'https://basecamp.com' | ||
end | ||
end | ||
|
||
test "disallows URL target with different protocol than source" do | ||
assert_raises GoogleSignIn::RedirectProtector::Violation do | ||
GoogleSignIn::RedirectProtector.ensure_same_origin 'http://basecamp.com', 'https://basecamp.com' | ||
end | ||
end | ||
|
||
test "allows URL target with same origin as source" do | ||
assert_nothing_raised do | ||
GoogleSignIn::RedirectProtector.ensure_same_origin 'https://basecamp.com', 'https://basecamp.com' | ||
end | ||
end | ||
|
||
test "allows path target" do | ||
assert_nothing_raised do | ||
GoogleSignIn::RedirectProtector.ensure_same_origin '/callback', 'https://basecamp.com' | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ | |
require 'active_support' | ||
require 'active_support/testing/autorun' | ||
require 'byebug' | ||
require 'google_sign_in' |