Skip to content

Commit 13dafac

Browse files
Switch to server-side OAuth 2.0
1 parent cb62598 commit 13dafac

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+1301
-143
lines changed

.gitignore

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.bundle/
2+
.byebug_history
3+
log/*.log
4+
pkg/
5+
6+
test/dummy/db/*.sqlite3
7+
test/dummy/db/*.sqlite3-journal
8+
test/dummy/log/*.log
9+
test/dummy/node_modules/
10+
test/dummy/yarn-error.log
11+
test/dummy/storage/
12+
test/dummy/tmp/

Gemfile.lock

+121-3
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,146 @@ PATH
22
remote: .
33
specs:
44
google_sign_in (0.1.4)
5-
activesupport (>= 5.1)
65
google-id-token (>= 1.4.0)
6+
oauth2 (>= 1.4.0)
7+
rails (>= 5.1.0)
78

89
GEM
910
remote: https://rubygems.org/
1011
specs:
11-
activesupport (5.2.1)
12+
actioncable (5.2.0)
13+
actionpack (= 5.2.0)
14+
nio4r (~> 2.0)
15+
websocket-driver (>= 0.6.1)
16+
actionmailer (5.2.0)
17+
actionpack (= 5.2.0)
18+
actionview (= 5.2.0)
19+
activejob (= 5.2.0)
20+
mail (~> 2.5, >= 2.5.4)
21+
rails-dom-testing (~> 2.0)
22+
actionpack (5.2.0)
23+
actionview (= 5.2.0)
24+
activesupport (= 5.2.0)
25+
rack (~> 2.0)
26+
rack-test (>= 0.6.3)
27+
rails-dom-testing (~> 2.0)
28+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
29+
actionview (5.2.0)
30+
activesupport (= 5.2.0)
31+
builder (~> 3.1)
32+
erubi (~> 1.4)
33+
rails-dom-testing (~> 2.0)
34+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
35+
activejob (5.2.0)
36+
activesupport (= 5.2.0)
37+
globalid (>= 0.3.6)
38+
activemodel (5.2.0)
39+
activesupport (= 5.2.0)
40+
activerecord (5.2.0)
41+
activemodel (= 5.2.0)
42+
activesupport (= 5.2.0)
43+
arel (>= 9.0)
44+
activestorage (5.2.0)
45+
actionpack (= 5.2.0)
46+
activerecord (= 5.2.0)
47+
marcel (~> 0.3.1)
48+
activesupport (5.2.0)
1249
concurrent-ruby (~> 1.0, >= 1.0.2)
1350
i18n (>= 0.7, < 2)
1451
minitest (~> 5.1)
1552
tzinfo (~> 1.1)
53+
addressable (2.5.2)
54+
public_suffix (>= 2.0.2, < 4.0)
55+
arel (9.0.0)
56+
builder (3.2.3)
1657
byebug (9.1.0)
1758
concurrent-ruby (1.0.5)
59+
crack (0.4.3)
60+
safe_yaml (~> 1.0.0)
61+
crass (1.0.4)
62+
erubi (1.7.1)
63+
faraday (0.12.2)
64+
multipart-post (>= 1.2, < 3)
65+
globalid (0.4.1)
66+
activesupport (>= 4.2.0)
1867
google-id-token (1.4.2)
1968
jwt (>= 1)
69+
hashdiff (0.3.7)
2070
i18n (1.1.0)
2171
concurrent-ruby (~> 1.0)
22-
jwt (2.1.0)
72+
jwt (1.5.6)
73+
loofah (2.2.2)
74+
crass (~> 1.0.2)
75+
nokogiri (>= 1.5.9)
76+
mail (2.7.0)
77+
mini_mime (>= 0.1.1)
78+
marcel (0.3.2)
79+
mimemagic (~> 0.3.2)
80+
method_source (0.9.0)
81+
mimemagic (0.3.2)
82+
mini_mime (1.0.0)
83+
mini_portile2 (2.3.0)
2384
minitest (5.11.3)
85+
multi_json (1.13.1)
86+
multi_xml (0.6.0)
87+
multipart-post (2.0.0)
88+
nio4r (2.3.1)
89+
nokogiri (1.8.4)
90+
mini_portile2 (~> 2.3.0)
91+
oauth2 (1.4.0)
92+
faraday (>= 0.8, < 0.13)
93+
jwt (~> 1.0)
94+
multi_json (~> 1.3)
95+
multi_xml (~> 0.5)
96+
rack (>= 1.2, < 3)
97+
public_suffix (3.0.3)
98+
rack (2.0.5)
99+
rack-test (1.1.0)
100+
rack (>= 1.0, < 3)
101+
rails (5.2.0)
102+
actioncable (= 5.2.0)
103+
actionmailer (= 5.2.0)
104+
actionpack (= 5.2.0)
105+
actionview (= 5.2.0)
106+
activejob (= 5.2.0)
107+
activemodel (= 5.2.0)
108+
activerecord (= 5.2.0)
109+
activestorage (= 5.2.0)
110+
activesupport (= 5.2.0)
111+
bundler (>= 1.3.0)
112+
railties (= 5.2.0)
113+
sprockets-rails (>= 2.0.0)
114+
rails-dom-testing (2.0.3)
115+
activesupport (>= 4.2.0)
116+
nokogiri (>= 1.6)
117+
rails-html-sanitizer (1.0.4)
118+
loofah (~> 2.2, >= 2.2.2)
119+
railties (5.2.0)
120+
actionpack (= 5.2.0)
121+
activesupport (= 5.2.0)
122+
method_source
123+
rake (>= 0.8.7)
124+
thor (>= 0.18.1, < 2.0)
24125
rake (12.0.0)
126+
safe_yaml (1.0.4)
127+
sprockets (3.7.2)
128+
concurrent-ruby (~> 1.0)
129+
rack (> 1, < 3)
130+
sprockets-rails (3.2.1)
131+
actionpack (>= 4.0)
132+
activesupport (>= 4.0)
133+
sprockets (>= 3.0.0)
134+
thor (0.20.0)
25135
thread_safe (0.3.6)
26136
tzinfo (1.2.5)
27137
thread_safe (~> 0.1)
138+
webmock (3.4.2)
139+
addressable (>= 2.3.6)
140+
crack (>= 0.3.2)
141+
hashdiff
142+
websocket-driver (0.7.0)
143+
websocket-extensions (>= 0.1.0)
144+
websocket-extensions (0.1.3)
28145

29146
PLATFORMS
30147
ruby
@@ -34,6 +151,7 @@ DEPENDENCIES
34151
byebug
35152
google_sign_in!
36153
rake
154+
webmock
37155

38156
BUNDLED WITH
39157
1.16.3

README.md

+112-47
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,149 @@
11
# Google Sign-In for Rails
22

3-
Google Sign-In provides an easy and secure way to let users signin into and up for your service,
4-
without adding yet-another per-app email/password combination. Integrating it into your Rails app
5-
should be drop-in easy. This gem makes it so.
3+
This gem allows you to add Google sign-in to your Rails app. You can let users sign up for and sign in to your service
4+
with their Google accounts.
65

7-
The only configuration needed is setting the Google client id for your application. [Google has a
8-
tutorial on how to setup a client id](https://developers.google.com/identity/sign-in/web/server-side-flow#step_1_create_a_client_id_and_client_secret).
96

10-
Once you have your client id, create a `config/initializers/google_sign_in_client_id.rb` file with this:
11-
`GoogleSignIn::Identity.client_id = <THAT CLIENT ID YOU GOT FROM GOOGLE>`
7+
## Installation
128

13-
Now you can use the sign-in integration on your signup or signin screen.
9+
Add `google_sign_in` to your Rails app’s Gemfile and run `bundle install`:
1410

15-
## Example
11+
```ruby
12+
gem 'google_sign_in'
13+
```
14+
15+
16+
## Configuration
17+
18+
First, set up an OAuth 2.0 Client ID in the Google API Console:
19+
20+
1. Go to the [API Console](https://console.developers.google.com/apis/credentials).
21+
22+
2. In the projects menu at the top of the page, ensure the correct project is selected or create a new one.
23+
24+
3. In the left-side navigation menu, choose APIs & Services → Credentials.
25+
26+
4. Click the button labeled “Create credentials.” In the menu that appears, choose to create an **OAuth client ID**.
27+
28+
5. When prompted to select an application type, select **Web application**.
29+
30+
6. Enter your application’s name.
1631

17-
Here's the most basic example:
32+
7. This gem adds a single OAuth callback to your app at `/google_sign_in/callback`. Under **Authorized redirect URIs**,
33+
add that callback for your application’s domain: for example, `https://example.com/google_sign_in/callback`.
34+
35+
To use Google sign-in in development, you’ll need to add another redirect URI for your local environment, like
36+
`http://localhost:3000/google_sign_in/callback`. For security reasons, we recommend using a separate
37+
client ID for local development. Repeat these instructions to set up a new client ID for development.
38+
39+
8. Click the button labeled “Create.” You’ll be presented with a client ID and client secret. Save these.
40+
41+
With your client ID set up, configure your Rails application. In a new initializer, provide the client ID and client secret:
42+
43+
```ruby
44+
# config/initializers/google_sign_in.rb
45+
Rails.application.configure do
46+
config.google_sign_in.client_id = '...'
47+
config.google_sign_in.client_secret = '...'
48+
end
49+
```
50+
51+
**⚠️ Important:** Take care to protect your client secret. Consider storing your Google client ID and client secret in
52+
your Rails application’s [encrypted credentials file](https://guides.rubyonrails.org/security.html#custom-credentials):
53+
54+
```yaml
55+
# rails credentials:edit
56+
google_sign_in_client_id: ...
57+
google_sign_in_client_secret: ...
58+
```
1859
1960
```ruby
20-
# app/views/layouts/application.html.erb
21-
<html>
22-
<head>
23-
<% # Required for google_sign_in to add the Google JS assets and meta tags! %>
24-
<%= yield :head %>
25-
</head>
26-
<body>
27-
<%= yield %>
28-
</body>
29-
</html>
30-
31-
# app/views/sessions/new.html.erb
32-
<%= google_sign_in(url: session_path) do %>
33-
# You can replace this with whatever design you please for the button.
34-
# You should follow Google's brand guidelines for Google Sign-In, though:
35-
# https://developers.google.com/identity/branding-guidelines
36-
<%= button_tag("Signin with Google") %>
61+
# config/initializers/google_sign_in.rb
62+
Rails.application.configure do
63+
config.google_sign_in.client_id = Rails.application.credentials.google_sign_in_client_id
64+
config.google_sign_in.client_secret = Rails.application.credentials.google_sign_in_client_secret
65+
end
66+
```
67+
68+
Alternatively, provide the client ID and client secret using ENV variables:
69+
70+
```ruby
71+
# config/initializers/google_sign_in.rb
72+
Rails.application.configure do
73+
config.google_sign_in.client_id = ENV['google_sign_in_client_id']
74+
config.google_sign_in.client_secret = ENV['google_sign_in_client_secret']
75+
end
76+
```
77+
78+
## Usage
79+
80+
This gem provides a `google_sign_in_button` helper. It generates a button which initiates Google sign-in:
81+
82+
```erb
83+
<%= google_sign_in_button 'Sign in with my Google account', proceed_to: create_login_url %>
84+
85+
<%= google_sign_in_button image_tag('google_logo.png', alt: 'Google'), proceed_to: create_login_url %>
86+
87+
<%= google_sign_in_button proceed_to: create_login_url do %>
88+
Sign in with my <%= image_tag('google_logo.png', alt: 'Google') %> account
3789
<% end %>
3890
```
3991

40-
The `url` option is the URL that the hidden form will be submitted against along with the Google ID Token
41-
that's set after the user has picked the account and authenticated in the pop-up window Google provides.
92+
The `proceed_to` argument is required. After authenticating with Google, the gem redirects to `proceed_to`, providing
93+
a Google ID token in `flash[:google_sign_in_token]`. Your application decides what to do with it:
4294

43-
You can then use that in a sessions controller like so:
95+
```ruby
96+
# config/routes.rb
97+
Rails.application.routes.draw do
98+
# ...
99+
get 'login', to: 'logins#new'
100+
get 'login/create', to: 'logins#create', as: :create_login
101+
end
102+
```
44103

45104
```ruby
46-
class SessionsController < ApplicationController
105+
# app/controllers/logins_controller.rb
106+
class LoginsController < ApplicationController
47107
def new
48108
end
49109

50110
def create
51-
if user = authenticate_via_google
111+
if user = authenticate_with_google
52112
cookies.signed[:user_id] = user.id
53113
redirect_to user
54114
else
55-
redirect_to new_session_url, alert: "authentication_failed"
115+
redirect_to new_session_url, alert: 'authentication_failed'
56116
end
57117
end
58118

59119
private
60-
def authenticate_via_google
61-
if params[:google_id_token].present?
62-
User.find_by google_id: GoogleSignIn::Identity.new(params[:google_id_token]).user_id
120+
def authenticate_with_google
121+
if flash[:google_sign_in_token].present?
122+
User.find_by google_id: GoogleSignIn::Identity.new(flash[:google_sign_in_token]).user_id
63123
end
64124
end
65125
end
66126
```
67127

68-
(This example assumes that a user has already signed up for your service using Google Sign-In and that
69-
you're storing the Google user id in the `User#google_id` attribute).
128+
(The above example assumes the user has already signed up for your service and that you’re storing their Google user ID
129+
in the `User#google_id` attribute.)
130+
131+
The `GoogleSignIn::Identity` class decodes and verifies the integrity of a Google ID token. It exposes the profile
132+
information contained in the token via the following instance methods:
133+
134+
* `name`
135+
136+
* `email_address`
137+
138+
* `user_id`: A value that uniquely identifies a single Google user. Use this, not `email_address`, to associate a
139+
Google user with an application user. A Google user’s email address may change, but their `user_id` will remain constant.
140+
141+
* `email_verified?`
70142

71-
That's it! You can checkout the `GoogleSignIn::Identity` class for the thin wrapping it provides around
72-
the decoding of the Google ID Token using the google-id-token library. Interrogating this identity object
73-
for profile details is particularly helpful when you use Google for signup, as you can get the name, email
74-
address, avatar url, and locale through it.
143+
* `avatar_url`
75144

76-
## Compatibility with Turbolinks
145+
* `locale`
77146

78-
Google's JavaScript doesn't play nice with Turbolinks. We've routed around the damage by adding a [Turbolinks
79-
meta tag](https://github.com/turbolinks/turbolinks/blob/master/README.md#ensuring-specific-pages-trigger-a-full-reload)
80-
on whatever page `google_sign_in` is called to always do a full reload for that page. Note that this
81-
auto-compatibility feature requires Turbolinks 5.1+.
82147

83148
## License
84149

Rakefile

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ require "rake/testtask"
44

55
Rake::TestTask.new do |test|
66
test.libs << "test"
7-
test.test_files = FileList["test/*_test.rb"]
7+
test.test_files = FileList["test/**/*_test.rb"]
8+
test.warning = false
89
end
910

1011
task default: :test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
require 'securerandom'
2+
3+
class GoogleSignIn::AuthorizationsController < GoogleSignIn::BaseController
4+
def create
5+
redirect_to login_url(scope: 'openid profile email', state: state),
6+
flash: { proceed_to: params.require(:proceed_to), state: state }
7+
end
8+
9+
private
10+
def login_url(**params)
11+
client.auth_code.authorize_url(prompt: 'login', **params)
12+
end
13+
14+
def state
15+
@state ||= SecureRandom.base64(16)
16+
end
17+
end

0 commit comments

Comments
 (0)