omniauth-google-oauth2 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb04de5f033f4c247c0cd004619d2e8cbb9f54b29a1861c5810d539608c278c0
4
- data.tar.gz: a0a71c285455501e4904ea4c184db32298907be93f1def96d3a180ca60df9684
3
+ metadata.gz: 2bbffd3ded3fae87a753e2111d08282928af98cbd5cf2f478e0995b438d578dd
4
+ data.tar.gz: 937f939cb313ebe581318183d452bc62379a430f7b5f5e52331ef066bf3f77eb
5
5
  SHA512:
6
- metadata.gz: 3cd913d3979e3c3e9dd93f76ed40aeff42bc95a8841db4b4287e92d28a00fe85a4bbc14483b25478fbdde0552877617d4b32c57eb03c67472e43fd857c1e9180
7
- data.tar.gz: '0078d9d52c2661b12895509ce17320818360aa968904742d45665158334af0a219064999cabe71e1af9ce93cddd45481b7aee263d729d582f99d088b68242905'
6
+ metadata.gz: d96c30940663274f66294fb6ad29c121f413dec3f95b3707af84d47713a52b21e51ac4d11fa4f8bfd8cb9b16f62fb74115a5fb841d8598714f1f8f530427ef2c
7
+ data.tar.gz: 2dc76d7caf6b605347236be49750ad28ec64ba81a001f19216a9748cc986b9ffb8c7afa88081c3e7403a1ed239f7820d6252267ca7defa5f40348f1290dafa4d
@@ -1,4 +1,4 @@
1
- name: CI
1
+ name: CI
2
2
 
3
3
  on: [push, pull_request]
4
4
 
@@ -6,16 +6,17 @@ jobs:
6
6
  test:
7
7
  runs-on: ubuntu-latest
8
8
  strategy:
9
+ fail-fast: false
9
10
  matrix:
10
- ruby-version: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2', truffleruby-head]
11
+ ruby-version: ['2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0', truffleruby-head]
11
12
 
12
13
  steps:
13
- - uses: actions/checkout@v2
14
+ - uses: actions/checkout@v6
14
15
  - name: Set up Ruby ${{ matrix.ruby-version }}
15
16
  uses: ruby/setup-ruby@v1
16
17
  with:
17
18
  ruby-version: ${{ matrix.ruby-version }}
18
19
  bundler-cache: true # 'bundle install' and cache
19
- - name: Run specs
20
+ - name: Run specs
20
21
  run: |
21
22
  bundle exec rake
@@ -0,0 +1,19 @@
1
+ name: rubocop
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ rubocop:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v6
12
+ - name: Set up Ruby 3.4
13
+ uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: '3.4'
16
+ - name: Lint Ruby code with RuboCop
17
+ run: |
18
+ bundle install
19
+ bundle exec rubocop --parallel
data/.rubocop.yml CHANGED
@@ -1,9 +1,14 @@
1
+ AllCops:
2
+ NewCops: disable
3
+ SuggestExtensions: false
4
+ TargetRubyVersion: 2.5
5
+
1
6
  Metrics/ClassLength:
2
7
  Enabled: false
3
8
  Metrics/AbcSize:
4
9
  Enabled: false
5
10
  Metrics/BlockLength:
6
- ExcludedMethods: ['describe', 'context', 'shared_examples']
11
+ AllowedMethods: ['describe', 'context', 'shared_examples']
7
12
  Metrics/CyclomaticComplexity:
8
13
  Enabled: false
9
14
  Layout/LineLength:
@@ -18,6 +23,8 @@ Style/MutableConstant:
18
23
  Enabled: false
19
24
  Gemspec/RequiredRubyVersion:
20
25
  Enabled: false
26
+ Gemspec/RequireMFA:
27
+ Enabled: false
21
28
  Lint/RaiseException:
22
29
  Enabled: false
23
30
  Lint/StructNewOverride:
@@ -28,5 +35,5 @@ Style/HashTransformKeys:
28
35
  Enabled: false
29
36
  Style/HashTransformValues:
30
37
  Enabled: false
31
- AllCops:
32
- NewCops: enable
38
+ Style/FetchEnvVar:
39
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,6 +1,23 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## 1.2.2 - Unreleased
5
+
6
+ ### Added
7
+ - Ruby 4.0 support in CI.
8
+
9
+ ### Deprecated
10
+ - Nothing.
11
+
12
+ ### Removed
13
+ - Unused `IMAGE_SIZE_REGEXP` constant.
14
+ - Dead `skip_friends` and `skip_image_info` options (Google+ was shut down in 2019).
15
+ - `CGI.parse` dependency replaced with `URI.decode_www_form` for Ruby 4.0 compatibility.
16
+
17
+ ### Fixed
18
+ - Updated gemspec description to reference OmniAuth instead of OmniAuth 1.x.
19
+ - Modernized CI: bumped actions/checkout to v6, rake to 13.3, and rubocop to latest.
20
+
4
21
  ## 1.2.1 - 2025-01-18
5
22
 
6
23
  ### Added
data/Gemfile CHANGED
@@ -3,3 +3,5 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
+
7
+ gem 'rubocop'
data/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  Strategy to authenticate with Google via OAuth2 in OmniAuth.
6
6
 
7
- Get your API key at: https://code.google.com/apis/console/ Note the Client ID and the Client Secret.
7
+ Get your API key at: https://console.cloud.google.com Note the Client ID and the Client Secret.
8
8
 
9
- For more details, read the Google docs: https://developers.google.com/accounts/docs/OAuth2
9
+ For more details, read the Google docs: https://developers.google.com/identity/protocols/oauth2
10
10
 
11
11
  ## Installation
12
12
 
@@ -20,7 +20,7 @@ Then `bundle install`.
20
20
 
21
21
  ## Google API Setup
22
22
 
23
- * Go to 'https://console.developers.google.com'
23
+ * Go to 'https://console.cloud.google.com'
24
24
  * Select your project.
25
25
  * Go to Credentials, then select the "OAuth consent screen" tab on top, and provide an 'EMAIL ADDRESS' and a 'PRODUCT NAME'
26
26
  * Wait 10 minutes for changes to take effect.
@@ -76,11 +76,11 @@ You can configure several options, which you pass in to the `provider` method vi
76
76
 
77
77
  * `hd`: (Optional) Limit sign-in to a particular Google Apps hosted domain. This can be simply string `'domain.com'` or an array `%w(domain.com domain.co)`. More information at: https://developers.google.com/accounts/docs/OpenIDConnect#hd-param
78
78
 
79
- * `jwt_leeway`: Number of seconds passed to the JWT library as leeway. Defaults to 60 seconds. Note this only works if you use jwt 2.1, as the leeway option was removed in later versions.
79
+ * `jwt_leeway`: Number of seconds passed to the JWT library as leeway. Defaults to 60 seconds.
80
80
 
81
81
  * `skip_jwt`: Skip JWT processing. This is for users who are seeing JWT decoding errors with the `iat` field. Always try adjusting the leeway before disabling JWT processing.
82
82
 
83
- * `login_hint`: When your app knows which user it is trying to authenticate, it can provide this parameter as a hint to the authentication server. Passing this hint suppresses the account chooser and either pre-fill the email box on the sign-in form, or select the proper session (if the user is using multiple sign-in), which can help you avoid problems that occur if your app logs in the wrong user account. The value can be either an email address or the sub string, which is equivalent to the user's Google+ ID.
83
+ * `login_hint`: When your app knows which user it is trying to authenticate, it can provide this parameter as a hint to the authentication server. Passing this hint suppresses the account chooser and either pre-fill the email box on the sign-in form, or select the proper session (if the user is using multiple sign-in), which can help you avoid problems that occur if your app logs in the wrong user account. The value can be either an email address or the `sub` string (the user's unique Google ID).
84
84
 
85
85
  * `include_granted_scopes`: If this is provided with the value true, and the authorization request is granted, the authorization will include any previous authorizations granted to this user/application combination for other scopes. See Google's [Incremental Authorization](https://developers.google.com/accounts/docs/OAuth2WebServer#incrementalAuth) for additional details.
86
86
 
@@ -121,7 +121,7 @@ Here's an example of an authentication hash available in the callback by accessi
121
121
  "last_name" => "Smith",
122
122
  "image" => "https://lh4.googleusercontent.com/photo.jpg",
123
123
  "urls" => {
124
- "google" => "https://plus.google.com/+JohnSmith"
124
+ "google" => "https://profiles.google.com/100000000000000000000"
125
125
  }
126
126
  },
127
127
  "credentials" => {
@@ -148,7 +148,7 @@ Here's an example of an authentication hash available in the callback by accessi
148
148
  "name" => "John Smith",
149
149
  "given_name" => "John",
150
150
  "family_name" => "Smith",
151
- "profile" => "https://plus.google.com/+JohnSmith",
151
+ "profile" => "https://profiles.google.com/100000000000000000000",
152
152
  "picture" => "https://lh4.googleusercontent.com/photo.jpg?sz=50",
153
153
  "email" => "john@example.com",
154
154
  "email_verified" => "true",
@@ -227,26 +227,61 @@ end
227
227
  For your views you can login using:
228
228
 
229
229
  ```erb
230
- <%# omniauth-google-oauth2 1.0.x uses OmniAuth 2 and requires using HTTP Post to initiate authentication: %>
231
230
  <%= link_to "Sign in with Google", user_google_oauth2_omniauth_authorize_path, method: :post %>
231
+ ```
232
+
233
+ An overview is available at https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview
234
+
235
+ #### Note about multi-platform authentication (Web, Android, IOS, ...)
236
+
237
+ If you authenticate your user from multiple different platforms with a single API you will likely have different Google `client_id` depending on the platform.
238
+
239
+ This could raise errors in the callback step because the `client_id` used in the callback needs to be the same as the one used in the sign in request.
232
240
 
233
- <%# omniauth-google-oauth2 prior 1.0.0: %>
234
- <%= link_to "Sign in with Google", user_google_oauth2_omniauth_authorize_path %>
241
+ To handle multiple `client_id` you can register multiple omniauth middlewares in your devise initializer with different names and different client ids. You can then register each middleware in your omniauthable model and add a new action in your `OmniauthCallbacksController` for each additional middleware.
235
242
 
236
- <%# Devise prior 4.1.0: %>
237
- <%= link_to "Sign in with Google", user_omniauth_authorize_path(:google_oauth2) %>
243
+ ```ruby
244
+ # config/initializers/devise.rb
245
+
246
+ config.omniauth :google_oauth2, 'GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_SECRET', { name: 'google_oauth2' }
247
+
248
+ # Native mobile applications don't require a `client_secret`
249
+ config.omniauth :google_oauth2, 'GOOGLE_CLIENT_ID_ANDROID', { name: 'google_oauth2_android' }
250
+ config.omniauth :google_oauth2, 'GOOGLE_CLIENT_ID_IOS', { name: 'google_oauth2_ios' }
238
251
  ```
239
252
 
240
- An overview is available at https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview
253
+ ```ruby
254
+ # app/models/user.rb
255
+
256
+ devise :omniauthable, omniauth_providers: %i[google_oauth2 google_oauth2_android google_oauth2_ios]
257
+ ```
258
+
259
+ ```ruby
260
+ # app/controllers/users/omniauth_callbacks_controller.rb:
261
+
262
+ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
263
+ def google_oauth2
264
+ # ...
265
+ end
266
+
267
+ def google_oauth2_android
268
+ # ...
269
+ end
270
+
271
+ def google_oauth2_ios
272
+ # ...
273
+ end
274
+ end
275
+ ```
241
276
 
242
277
  ### One-time Code Flow (Hybrid Authentication)
243
278
 
244
- Google describes the One-time Code Flow [here](https://developers.google.com/identity/sign-in/web/server-side-flow). This hybrid authentication flow has significant functional and security advantages over a pure server-side or pure client-side flow. The following steps occur in this flow:
279
+ Google describes the One-time Code Flow [here](https://developers.google.com/identity/protocols/oauth2). This hybrid authentication flow has significant functional and security advantages over a pure server-side or pure client-side flow. The following steps occur in this flow:
245
280
 
246
- 1. The client (web browser) authenticates the user directly via Google's JS API. During this process assorted modals may be rendered by Google.
281
+ 1. The client (web browser) authenticates the user directly via Google's OAuth 2 API. During this process assorted modals may be rendered by Google.
247
282
  2. On successful authentication, Google returns a one-time use code, which requires the Google client secret (which is only available server-side).
248
283
  3. Using a AJAX request, the code is POSTed to the Omniauth Google OAuth2 callback.
249
- 4. The Omniauth Google OAuth2 gem will validate the code via a server-side request to Google. If the code is valid, then Google will return an access token and, if this is the first time this user is authenticating against this application, a refresh token. Both of these should be stored on the server. The response to the AJAX request indicates the success or failure of this process.
284
+ 4. The Omniauth Google OAuth2 gem will validate the code via a server-side request to Google. If the code is valid, then Google will return an access token and, if this is the first time this user is authenticating against this application, a refresh token. Both of these should be stored on the server. The response to the AJAX request indicates the success or failure of this process.
250
285
 
251
286
  This flow is immune to replay attacks, and conveys no useful information to a man in the middle.
252
287
 
@@ -254,38 +289,52 @@ The omniauth-google-oauth2 gem supports this mode of operation when `provider_ig
254
289
 
255
290
  ```javascript
256
291
  // Basic hybrid auth example following the pattern at:
257
- // https://developers.google.com/identity/sign-in/web/reference
258
-
259
- <script src="https://apis.google.com/js/platform.js?onload=init" async defer></script>
260
-
261
- ...
262
-
263
- function init() {
264
- gapi.load('auth2', function() {
265
- // Ready.
266
- $('.google-login-button').click(function(e) {
267
- e.preventDefault();
268
-
269
- gapi.auth2.authorize({
270
- client_id: 'YOUR_CLIENT_ID',
271
- cookie_policy: 'single_host_origin',
272
- scope: 'email profile',
273
- response_type: 'code'
274
- }, function(response) {
275
- if (response && !response.error) {
276
- // google authentication succeed, now post data to server.
277
- jQuery.ajax({type: 'POST', url: '/auth/google_oauth2/callback', data: response,
278
- success: function(data) {
279
- // response from server
280
- }
281
- });
282
- } else {
283
- // google authentication failed
284
- }
285
- });
286
- });
292
+ // https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow
293
+
294
+ const handleGoogleOauthSignIn = () => {
295
+ // Google's OAuth 2.0 endpoint for requesting an access token
296
+ const oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
297
+
298
+ // Parameters to pass to OAuth 2.0 endpoint.
299
+ const params = new URLSearchParams({
300
+ client_id: YOUR_CLIENT_ID,
301
+ prompt: 'select_account',
302
+ redirect_uri: YOUR_REDIRECT_URI, // This redirect_uri needs to redirect to the same domain as the one where this request is made from.
303
+ response_type: 'code',
304
+ scope: 'email openid profile',
305
+ state: 'google', // The state will be added in the redirect_uri's query params. Use can this if you use the same redirect_uri with different omniauth provider to know which one you're currently handling for example.
306
+ });
307
+
308
+ const url = `${oauth2Endpoint}?${params.toString()}`;
309
+
310
+ // Create <a> element to redirect to OAuth 2.0 endpoint.
311
+ const a = document.createElement('a');
312
+ a.href = url;
313
+ a.target = '_self';
314
+
315
+ // Add a to page and click it to open the OAuth 2.0 endpoint.
316
+ document.body.appendChild(a);
317
+ a.click();
318
+ }
319
+
320
+ // Call this method when redirected to your `redirect_uri`
321
+ const handleGoogleOauthCallback = async () => {
322
+ // Get the query params Google included in your `redirect_uri`
323
+ const params = new URL(document.location.toString()).searchParams;
324
+ const code = params.get('code');
325
+ const state = params.get('state') // the `state` you added in the sign in request is here if you need it.
326
+
327
+ const response = fetch('your.api.domain/auth/google_oauth2/callback', {
328
+ body: JSON.stringify({
329
+ code,
330
+ redirect_uri: YOUR_REDIRECT_URI, // The `redirect_uri` used in the server needs to be the same as as initially used in the client.
331
+ }),
332
+ headers: {
333
+ 'Content-type': 'application/json',
334
+ },
335
+ method: 'POST',
287
336
  });
288
- };
337
+ }
289
338
  ```
290
339
 
291
340
  #### Note about mobile clients (iOS, Android)
@@ -298,66 +347,6 @@ In that case, ensure to send an additional parameter `redirect_uri=` (empty stri
298
347
 
299
348
  If you're making POST requests to `/auth/google_oauth2/callback` from another domain, then you need to make sure `'X-Requested-With': 'XMLHttpRequest'` header is included with your request, otherwise your server might respond with `OAuth2::Error, : Invalid Value` error.
300
349
 
301
- #### Getting around the `redirect_uri_mismatch` error (See [Issue #365](https://github.com/zquestz/omniauth-google-oauth2/issues/365))
302
-
303
- If you are struggling with a persistent `redirect_uri_mismatch`, you can instead pass the `access_token` from [`getAuthResponse`](https://developers.google.com/identity/sign-in/web/reference#googleusergetauthresponseincludeauthorizationdata) directly to the `auth/google_oauth2/callback` endpoint, like so:
304
-
305
- ```javascript
306
- // Initialize the GoogleAuth object
307
- let googleAuth;
308
- gapi.load('client:auth2', async () => {
309
- await gapi.client.init({ scope: '...', client_id: '...' });
310
- googleAuth = gapi.auth2.getAuthInstance();
311
- });
312
-
313
- // Call this when the Google Sign In button is clicked
314
- async function signInGoogle() {
315
- const googleUser = await googleAuth.signIn(); // wait for the user to authorize through the modal
316
- const { access_token } = googleUser.getAuthResponse();
317
-
318
- const data = new FormData();
319
- data.append('access_token', access_token);
320
-
321
- const response = await api.post('/auth/google_oauth2/callback', data)
322
- console.log(response);
323
- }
324
- ```
325
-
326
- #### Using Axios
327
- If you're making a GET resquests from another domain using `access_token`.
328
- ```
329
- axios
330
- .get(
331
- 'url(path to your callback}',
332
- { params: { access_token: 'token' } },
333
- headers....
334
- )
335
- ```
336
-
337
- If you're making a POST resquests from another domain using `access_token`.
338
- ```
339
- axios
340
- .post(
341
- 'url(path to your callback}',
342
- { access_token: 'token' },
343
- headers....
344
- )
345
-
346
- --OR--
347
-
348
- axios
349
- .post(
350
- 'url(path to your callback}',
351
- null,
352
- {
353
- params: {
354
- access_token: 'token'
355
- },
356
- headers....
357
- }
358
- )
359
- ```
360
-
361
350
  ## Fixing Protocol Mismatch for `redirect_uri` in Rails
362
351
 
363
352
  Just set the `full_host` in OmniAuth based on the Rails.env.
@@ -369,7 +358,7 @@ OmniAuth.config.full_host = Rails.env.production? ? 'https://domain.com' : 'http
369
358
 
370
359
  ## License
371
360
 
372
- Copyright (c) 2018 by Josh Ellithorpe
361
+ Copyright (c) 2018-2026 by Josh Ellithorpe
373
362
 
374
363
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
375
364
 
data/_config.yml ADDED
@@ -0,0 +1,3 @@
1
+ remote_theme: pages-themes/minimal@v0.2.0
2
+ plugins:
3
+ - jekyll-remote-theme
data/examples/Gemfile CHANGED
@@ -3,6 +3,7 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gem 'omniauth-google-oauth2', path: '..'
6
+ gem 'rackup'
6
7
  gem 'rubocop'
7
8
  gem 'sinatra'
8
9
  gem 'webrick'
data/examples/config.ru CHANGED
@@ -29,7 +29,7 @@ class App < Sinatra::Base
29
29
  use OmniAuth::Builder do
30
30
  # For additional provider examples please look at 'omni_auth.rb'
31
31
  # The key provider_ignores_state is only for AJAX flows. It is not recommended for normal logins.
32
- provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], access_type: 'offline', prompt: 'consent', provider_ignores_state: true, scope: 'email,profile,calendar'
32
+ provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], access_type: 'offline', prompt: 'consent', provider_ignores_state: true, scope: 'email,profile'
33
33
  end
34
34
 
35
35
  get '/' do
@@ -38,62 +38,77 @@ class App < Sinatra::Base
38
38
  <html>
39
39
  <head>
40
40
  <title>Google OAuth2 Example</title>
41
- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
41
+ </head>
42
+
43
+ <body>
44
+ <ul>
45
+ <li>
46
+ <form method="post" action="/auth/google_oauth2">
47
+ <input type="hidden" name="authenticity_token" value="#{request.env['rack.session']['csrf']}">
48
+ <button type="submit">Login with Google</button>
49
+ </form>
50
+ </li>
51
+
52
+ <li>
53
+ <a href="#" class="googleplus-login">Sign in with Google via AJAX</a>
54
+ </li>
55
+ </ul>
56
+
42
57
  <script>
43
- jQuery(function() {
44
- return $.ajax({
45
- url: 'https://apis.google.com/js/client:plus.js?onload=gpAsyncInit',
46
- dataType: 'script',
47
- cache: true
48
- });
49
- });
58
+ const a = document.querySelector('.googleplus-login');
50
59
 
51
- window.gpAsyncInit = function() {
52
- gapi.auth.authorize({
53
- immediate: true,
54
- response_type: 'code',
55
- cookie_policy: 'single_host_origin',
60
+ const handleGoogleOauthSignIn = () => {
61
+ const oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
62
+
63
+ const params = new URLSearchParams({
56
64
  client_id: '#{ENV['GOOGLE_KEY']}',
57
- scope: 'email profile'
58
- }, function(response) {
59
- return;
60
- });
61
- $('.googleplus-login').click(function(e) {
62
- e.preventDefault();
63
- gapi.auth.authorize({
64
- immediate: false,
65
- response_type: 'code',
66
- cookie_policy: 'single_host_origin',
67
- client_id: '#{ENV['GOOGLE_KEY']}',
68
- scope: 'email profile'
69
- }, function(response) {
70
- if (response && !response.error) {
71
- // google authentication succeed, now post data to server.
72
- jQuery.ajax({type: 'POST', url: "/auth/google_oauth2/callback", data: response,
73
- success: function(data) {
74
- // Log the data returning from google.
75
- console.log(data)
76
- }
77
- });
78
- } else {
79
- // google authentication failed.
80
- console.log("FAILED")
81
- }
82
- });
65
+ prompt: 'select_account',
66
+ redirect_uri: 'http://localhost:3000/callback',
67
+ response_type: 'code',
68
+ scope: 'email openid profile',
83
69
  });
84
- };
70
+
71
+ const url = `${oauth2Endpoint}?${params.toString()}`;
72
+ window.location.href = url;
73
+ }
74
+
75
+ a.addEventListener('click', event => {
76
+ event.preventDefault();
77
+ handleGoogleOauthSignIn();
78
+ });
85
79
  </script>
80
+ </body>
81
+ </html>
82
+ HTML
83
+ end
84
+
85
+ get '/callback' do
86
+ <<-HTML
87
+ <!DOCTYPE html>
88
+ <html>
89
+ <head>
90
+ <title>Google OAuth2 Example</title>
86
91
  </head>
92
+
87
93
  <body>
88
- <ul>
89
- <li>
90
- <form method='post' action='/auth/google_oauth2'>
91
- <input type="hidden" name="authenticity_token" value="#{request.env['rack.session']['csrf']}">
92
- <button type='submit'>Login with Google</button>
93
- </form>
94
- </li>
95
- <li><a href='#' class="googleplus-login">Sign in with Google via AJAX</a></li>
96
- </ul>
94
+ <p>Redirected</p>
95
+
96
+ <script>
97
+ const handleGoogleOauthCallback = async () => {
98
+ const params = new URL(document.location.toString()).searchParams;
99
+ const code = params.get('code');
100
+
101
+ const response = fetch('http://localhost:3000/auth/google_oauth2/callback', {
102
+ body: JSON.stringify({ code, redirect_uri: 'http://localhost:3000/callback' }),
103
+ headers: {
104
+ 'Content-type': 'application/json',
105
+ },
106
+ method: 'POST',
107
+ });
108
+ }
109
+
110
+ handleGoogleOauthCallback();
111
+ </script>
97
112
  </body>
98
113
  </html>
99
114
  HTML
@@ -2,36 +2,28 @@
2
2
 
3
3
  # Google's OAuth2 docs. Make sure you are familiar with all the options
4
4
  # before attempting to configure this gem.
5
- # https://developers.google.com/accounts/docs/OAuth2Login
5
+ # https://developers.google.com/identity/protocols/oauth2
6
6
 
7
7
  Rails.application.config.middleware.use OmniAuth::Builder do
8
8
  # Default usage, this will give you offline access and a refresh token
9
9
  # using default scopes 'email' and 'profile'
10
10
  #
11
- provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], scope: 'email, profile'
11
+ provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], scope: 'email, profile'
12
12
 
13
13
  # Custom redirect_uri
14
14
  #
15
- # provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], scope: 'email, profile', redirect_uri: 'https://localhost:3000/redirect'
15
+ # provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], scope: 'email, profile', redirect_uri: 'https://localhost:3000/redirect'
16
16
 
17
17
  # Manual setup for offline access with a refresh token.
18
18
  #
19
- # provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], access_type: 'offline'
19
+ # provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], access_type: 'offline'
20
20
 
21
- # Custom scope supporting youtube. If you are customizing scopes, remember
21
+ # Custom scope supporting YouTube. If you are customizing scopes, remember
22
22
  # to include the default scopes 'email' and 'profile'
23
23
  #
24
- # provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], scope: 'http://gdata.youtube.com, email, profile, plus.me'
24
+ # provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], scope: 'https://www.googleapis.com/auth/youtube.readonly, email, profile'
25
25
 
26
26
  # Custom scope for users only using Google for account creation/auth and do not require a refresh token.
27
27
  #
28
- # provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], access_type: 'online', prompt: ''
29
-
30
- # To include information about people in your circles you must include the 'plus.login' scope.
31
- #
32
- # provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], skip_friends: false, scope: 'email, profile, plus.login'
33
-
34
- # If you need to acquire whether user picture is a default one or uploaded by user.
35
- #
36
- # provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], skip_image_info: false
28
+ # provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], access_type: 'online', prompt: ''
37
29
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAuth
4
4
  module GoogleOauth2
5
- VERSION = '1.2.1'
5
+ VERSION = '1.2.2'
6
6
  end
7
7
  end
@@ -14,12 +14,9 @@ module OmniAuth
14
14
  BASE_SCOPES = %w[profile email openid].freeze
15
15
  DEFAULT_SCOPE = 'email,profile'
16
16
  USER_INFO_URL = 'https://www.googleapis.com/oauth2/v3/userinfo'
17
- IMAGE_SIZE_REGEXP = /(s\d+(-c)?)|(w\d+-h\d+(-c)?)|(w\d+(-c)?)|(h\d+(-c)?)|c/
18
17
  AUTHORIZE_OPTIONS = %i[access_type hd login_hint prompt request_visible_actions scope state redirect_uri include_granted_scopes enable_granular_consent openid_realm device_id device_name]
19
18
 
20
19
  option :name, 'google_oauth2'
21
- option :skip_friends, true
22
- option :skip_image_info, true
23
20
  option :skip_jwt, false
24
21
  option :jwt_leeway, 60
25
22
  option :authorize_options, AUTHORIZE_OPTIONS
@@ -149,7 +146,7 @@ module OmniAuth
149
146
 
150
147
  def get_scope(params)
151
148
  raw_scope = params[:scope] || DEFAULT_SCOPE
152
- scope_list = raw_scope.split(' ').map { |item| item.split(',') }.flatten
149
+ scope_list = raw_scope.split.map { |item| item.split(',') }.flatten
153
150
  scope_list.map! { |s| s =~ %r{^https?://} || BASE_SCOPES.include?(s) ? s : "#{BASE_SCOPE_URL}#{s}" }
154
151
  scope_list.join(' ')
155
152
  end
@@ -212,8 +209,8 @@ module OmniAuth
212
209
  # strip `sz` parameter (defaults to sz=50) which overrides `image_size` options
213
210
  return nil if query_parameters.nil?
214
211
 
215
- params = CGI.parse(query_parameters)
216
- stripped_params = params.delete_if { |key| key == 'sz' }
212
+ params = URI.decode_www_form(query_parameters)
213
+ stripped_params = params.delete_if { |key, _value| key == 'sz' }
217
214
 
218
215
  # don't return an empty Hash since that would result
219
216
  # in URLs with a trailing ? character: http://image.url?
@@ -9,8 +9,8 @@ Gem::Specification.new do |gem|
9
9
  gem.name = 'omniauth-google-oauth2'
10
10
  gem.version = OmniAuth::GoogleOauth2::VERSION
11
11
  gem.license = 'MIT'
12
- gem.summary = %(A Google OAuth2 strategy for OmniAuth 1.x)
13
- gem.description = %(A Google OAuth2 strategy for OmniAuth 1.x. This allows you to login to Google with your ruby app.)
12
+ gem.summary = %(A Google OAuth2 strategy for OmniAuth)
13
+ gem.description = %(A Google OAuth2 strategy for OmniAuth. This allows you to login to Google with your ruby app.)
14
14
  gem.authors = ['Josh Ellithorpe', 'Yury Korolev']
15
15
  gem.email = ['quest@mac.com']
16
16
  gem.homepage = 'https://github.com/zquestz/omniauth-google-oauth2'
@@ -20,12 +20,11 @@ Gem::Specification.new do |gem|
20
20
 
21
21
  gem.required_ruby_version = '>= 2.5'
22
22
 
23
- gem.add_runtime_dependency 'jwt', '>= 2.9.2'
24
- gem.add_runtime_dependency 'oauth2', '~> 2.0'
25
- gem.add_runtime_dependency 'omniauth', '~> 2.0'
26
- gem.add_runtime_dependency 'omniauth-oauth2', '~> 1.8'
23
+ gem.add_dependency 'jwt', '>= 2.9.2'
24
+ gem.add_dependency 'oauth2', '~> 2.0'
25
+ gem.add_dependency 'omniauth', '~> 2.0'
26
+ gem.add_dependency 'omniauth-oauth2', '~> 1.8'
27
27
 
28
- gem.add_development_dependency 'rake', '~> 12.0'
28
+ gem.add_development_dependency 'rake', '~> 13.3'
29
29
  gem.add_development_dependency 'rspec', '~> 3.6'
30
- gem.add_development_dependency 'rubocop', '~> 0.49'
31
30
  end
@@ -341,6 +341,23 @@ describe OmniAuth::Strategies::GoogleOauth2 do
341
341
  end
342
342
  end
343
343
 
344
+ describe '#uid' do
345
+ let(:client) do
346
+ OAuth2::Client.new('abc', 'def') do |builder|
347
+ builder.request :url_encoded
348
+ builder.adapter :test do |stub|
349
+ stub.get('/oauth2/v3/userinfo') { [200, { 'content-type' => 'application/json' }, '{"sub": "12345"}'] }
350
+ end
351
+ end
352
+ end
353
+ let(:access_token) { OAuth2::AccessToken.from_hash(client, { 'access_token' => 'a' }) }
354
+ before { allow(subject).to receive(:access_token).and_return(access_token) }
355
+
356
+ it 'should return the sub from raw_info as uid' do
357
+ expect(subject.uid).to eq('12345')
358
+ end
359
+ end
360
+
344
361
  describe '#info' do
345
362
  let(:client) do
346
363
  OAuth2::Client.new('abc', 'def') do |builder|
@@ -687,6 +704,24 @@ describe OmniAuth::Strategies::GoogleOauth2 do
687
704
  end
688
705
  end
689
706
 
707
+ describe 'strip_unnecessary_query_parameters' do
708
+ it 'should return nil when query_parameters is nil' do
709
+ expect(subject.send(:strip_unnecessary_query_parameters, nil)).to be_nil
710
+ end
711
+
712
+ it 'should return nil when sz is the only parameter' do
713
+ expect(subject.send(:strip_unnecessary_query_parameters, 'sz=50')).to be_nil
714
+ end
715
+
716
+ it 'should strip sz and return remaining parameters' do
717
+ expect(subject.send(:strip_unnecessary_query_parameters, 'sz=50&hello=true&life=42')).to eq('hello=true&life=42')
718
+ end
719
+
720
+ it 'should return all parameters when sz is not present' do
721
+ expect(subject.send(:strip_unnecessary_query_parameters, 'hello=true&life=42')).to eq('hello=true&life=42')
722
+ end
723
+ end
724
+
690
725
  describe 'build_access_token' do
691
726
  it 'should use a hybrid authorization request_uri if this is an AJAX request with a code parameter' do
692
727
  allow(request).to receive(:xhr?).and_return(true)
@@ -744,7 +779,7 @@ describe OmniAuth::Strategies::GoogleOauth2 do
744
779
  expect(subject).to receive(:client).and_return(client)
745
780
 
746
781
  token = subject.build_access_token
747
- expect(token).to be_instance_of(::OAuth2::AccessToken)
782
+ expect(token).to be_instance_of(OAuth2::AccessToken)
748
783
  expect(token.token).to eq('valid_access_token')
749
784
  expect(token.client).to eq(client)
750
785
  end
@@ -798,11 +833,22 @@ describe OmniAuth::Strategies::GoogleOauth2 do
798
833
  expect(subject).to receive(:verify_token).with('valid_access_token').and_return true
799
834
 
800
835
  token = subject.build_access_token
801
- expect(token).to be_instance_of(::OAuth2::AccessToken)
836
+ expect(token).to be_instance_of(OAuth2::AccessToken)
802
837
  expect(token.token).to eq('valid_access_token')
803
838
  expect(token.client).to eq(client)
804
839
  end
805
840
 
841
+ it 'should handle a malformed json request body gracefully' do
842
+ body = StringIO.new('not valid json{{{')
843
+
844
+ allow(request).to receive(:xhr?).and_return(false)
845
+ allow(request).to receive(:params).and_return({})
846
+ allow(request).to receive(:content_type).and_return('application/json')
847
+ allow(request).to receive(:body).and_return(body)
848
+
849
+ expect { subject.build_access_token }.to output(/JSON parse error/).to_stderr
850
+ end
851
+
806
852
  it 'should use callback_url without query_string if this is not an AJAX request' do
807
853
  allow(request).to receive(:xhr?).and_return(false)
808
854
  allow(request).to receive(:params).and_return('code' => 'valid_code')
@@ -856,6 +902,10 @@ describe OmniAuth::Strategies::GoogleOauth2 do
856
902
  expect(subject.send(:verify_token, 'valid_access_token')).to eq(false)
857
903
  end
858
904
 
905
+ it 'should return false if access_token is nil' do
906
+ expect(subject.send(:verify_token, nil)).to eq(false)
907
+ end
908
+
859
909
  it 'should raise error if access_token is invalid' do
860
910
  expect do
861
911
  subject.send(:verify_token, 'invalid_access_token')
@@ -945,5 +995,10 @@ describe OmniAuth::Strategies::GoogleOauth2 do
945
995
  subject.send(:verify_hd, access_token)
946
996
  end.to raise_error(OmniAuth::Strategies::GoogleOauth2::CallbackError)
947
997
  end
998
+
999
+ it 'should verify hd if options hd is set to wildcard *' do
1000
+ subject.options.hd = '*'
1001
+ expect(subject.send(:verify_hd, access_token)).to eq(true)
1002
+ end
948
1003
  end
949
1004
  end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-google-oauth2
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Ellithorpe
8
8
  - Yury Korolev
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2025-01-19 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: jwt
@@ -73,14 +72,14 @@ dependencies:
73
72
  requirements:
74
73
  - - "~>"
75
74
  - !ruby/object:Gem::Version
76
- version: '12.0'
75
+ version: '13.3'
77
76
  type: :development
78
77
  prerelease: false
79
78
  version_requirements: !ruby/object:Gem::Requirement
80
79
  requirements:
81
80
  - - "~>"
82
81
  - !ruby/object:Gem::Version
83
- version: '12.0'
82
+ version: '13.3'
84
83
  - !ruby/object:Gem::Dependency
85
84
  name: rspec
86
85
  requirement: !ruby/object:Gem::Requirement
@@ -95,22 +94,8 @@ dependencies:
95
94
  - - "~>"
96
95
  - !ruby/object:Gem::Version
97
96
  version: '3.6'
98
- - !ruby/object:Gem::Dependency
99
- name: rubocop
100
- requirement: !ruby/object:Gem::Requirement
101
- requirements:
102
- - - "~>"
103
- - !ruby/object:Gem::Version
104
- version: '0.49'
105
- type: :development
106
- prerelease: false
107
- version_requirements: !ruby/object:Gem::Requirement
108
- requirements:
109
- - - "~>"
110
- - !ruby/object:Gem::Version
111
- version: '0.49'
112
- description: A Google OAuth2 strategy for OmniAuth 1.x. This allows you to login to
113
- Google with your ruby app.
97
+ description: A Google OAuth2 strategy for OmniAuth. This allows you to login to Google
98
+ with your ruby app.
114
99
  email:
115
100
  - quest@mac.com
116
101
  executables: []
@@ -119,12 +104,14 @@ extra_rdoc_files: []
119
104
  files:
120
105
  - ".github/FUNDING.yml"
121
106
  - ".github/workflows/ci.yml"
107
+ - ".github/workflows/rubocop.yml"
122
108
  - ".gitignore"
123
109
  - ".rubocop.yml"
124
110
  - CHANGELOG.md
125
111
  - Gemfile
126
112
  - README.md
127
113
  - Rakefile
114
+ - _config.yml
128
115
  - examples/Gemfile
129
116
  - examples/config.ru
130
117
  - examples/omni_auth.rb
@@ -134,13 +121,11 @@ files:
134
121
  - lib/omniauth/strategies/google_oauth2.rb
135
122
  - omniauth-google-oauth2.gemspec
136
123
  - spec/omniauth/strategies/google_oauth2_spec.rb
137
- - spec/rubocop_spec.rb
138
124
  - spec/spec_helper.rb
139
125
  homepage: https://github.com/zquestz/omniauth-google-oauth2
140
126
  licenses:
141
127
  - MIT
142
128
  metadata: {}
143
- post_install_message:
144
129
  rdoc_options: []
145
130
  require_paths:
146
131
  - lib
@@ -155,8 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
140
  - !ruby/object:Gem::Version
156
141
  version: '0'
157
142
  requirements: []
158
- rubygems_version: 3.0.9
159
- signing_key:
143
+ rubygems_version: 3.6.9
160
144
  specification_version: 4
161
- summary: A Google OAuth2 strategy for OmniAuth 1.x
145
+ summary: A Google OAuth2 strategy for OmniAuth
162
146
  test_files: []
data/spec/rubocop_spec.rb DELETED
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'spec_helper'
4
-
5
- describe 'Rubocop' do
6
- it 'should pass with no offenses detected' do
7
- expect(`rubocop`).to include('no offenses detected')
8
- end
9
- end