standard_id-google 0.1.1 → 0.1.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: 77c483758db0802e58687b5c8ed4465ed8d4c5ecb1bd4af9f7078b6ed12d236b
4
- data.tar.gz: '00569352c386b2b28c6cfacbdda6640f2bcaa287af315c2d15820726bc7a89be'
3
+ metadata.gz: 54193c55f7d0359d865c030e3c4f42457d038a53bff4a3b3e78e46f792d577a2
4
+ data.tar.gz: 34ba47a877d286cf4cb8f5662e08665c78b03e6025c70b2490c87d536de27064
5
5
  SHA512:
6
- metadata.gz: 528bb72eb448ce2f3c4078d64b31d9d91ab6602cd176279ead86226849c295a25494af7cb4a8b0703260f90b47bf5a77d094b5b137eef5506c4e46158052f7d7
7
- data.tar.gz: 4cf122d55787bf6fa36902023afab87c4540c0a9824899447a7b0e81ce4082f9ca3a0f92d0f1d970067c61f7a52ab9638c97a7d7bd2fd3d5cd1d6f44a113c8a6
6
+ metadata.gz: a4e65705d26b442d36d2f362539cd9e305f429bc3cf2ed5cee39c31aca7f0e29f0f79fcbde2a2e6d48924ab15775a1c3b9103bc0b72d115520ed5fa714c51b80
7
+ data.tar.gz: 2ecded046bf8c360c8d61a448b72025f2e2baea42dc06c5a590a5bdd81185e95d4c48601c3893142968f3f7882f3152d8cf7fe356290c40bd7daf858653d519b
@@ -9,33 +9,38 @@ module StandardId
9
9
  USERINFO_ENDPOINT = "https://www.googleapis.com/oauth2/v2/userinfo".freeze
10
10
  TOKEN_INFO_ENDPOINT = "https://oauth2.googleapis.com/tokeninfo".freeze
11
11
  DEFAULT_SCOPE = "openid email profile".freeze
12
+ AUTHORIZATION_PARAM_DEFAULTS = {
13
+ scope: DEFAULT_SCOPE
14
+ }.freeze
12
15
 
13
16
  class << self
14
17
  def provider_name
15
18
  "google"
16
19
  end
17
20
 
18
- def authorization_url(state:, redirect_uri:, **options)
19
- scope = options[:scope] || DEFAULT_SCOPE
20
- prompt = options[:prompt]
21
+ def supported_authorization_params
22
+ [:nonce, :login_hint, :prompt, :scope, :access_type, :hd, :response_mode, :include_granted_scopes]
23
+ end
21
24
 
25
+ def authorization_url(state:, redirect_uri:, **options)
22
26
  query = {
23
27
  client_id: credentials[:client_id],
24
28
  redirect_uri: redirect_uri,
25
29
  response_type: "code",
26
- scope: scope,
27
30
  state: state
28
31
  }
29
32
 
30
- query[:prompt] = prompt if prompt.present?
33
+ supported_authorization_params.each do |param|
34
+ query[param] = options[param] || AUTHORIZATION_PARAM_DEFAULTS[param]
35
+ end
31
36
 
32
- "#{AUTH_ENDPOINT}?#{URI.encode_www_form(query)}"
37
+ "#{AUTH_ENDPOINT}?#{URI.encode_www_form(query.compact)}"
33
38
  end
34
39
 
35
- def get_user_info(code: nil, id_token: nil, access_token: nil, redirect_uri: nil, **_options)
40
+ def get_user_info(code: nil, id_token: nil, access_token: nil, redirect_uri: nil, nonce: nil, **_options)
36
41
  if id_token.present?
37
42
  build_response(
38
- verify_id_token(id_token: id_token),
43
+ verify_id_token(id_token: id_token, nonce: nonce),
39
44
  tokens: { id_token: id_token }
40
45
  )
41
46
  elsif access_token.present?
@@ -44,7 +49,7 @@ module StandardId
44
49
  tokens: { access_token: access_token }
45
50
  )
46
51
  elsif code.present?
47
- exchange_code_for_user_info(code: code, redirect_uri: redirect_uri)
52
+ exchange_code_for_user_info(code: code, redirect_uri: redirect_uri, nonce: nonce)
48
53
  else
49
54
  raise StandardId::InvalidRequestError, "Either code, id_token, or access_token must be provided"
50
55
  end
@@ -61,7 +66,7 @@ module StandardId
61
66
  DEFAULT_SCOPE
62
67
  end
63
68
 
64
- def exchange_code_for_user_info(code:, redirect_uri:)
69
+ def exchange_code_for_user_info(code:, redirect_uri:, nonce: nil)
65
70
  raise StandardId::InvalidRequestError, "Missing authorization code" if code.blank?
66
71
 
67
72
  token_response = HttpClient.post_form(TOKEN_ENDPOINT, {
@@ -80,6 +85,11 @@ module StandardId
80
85
  access_token = parsed_token["access_token"]
81
86
  raise StandardId::InvalidRequestError, "Google response missing access token" if access_token.blank?
82
87
 
88
+ # If we have an ID token in the response and a nonce was provided, verify it
89
+ if parsed_token["id_token"].present? && nonce.present?
90
+ verify_id_token(id_token: parsed_token["id_token"], nonce: nonce)
91
+ end
92
+
83
93
  tokens = extract_token_payload(parsed_token)
84
94
  user_info = fetch_user_info(access_token: access_token)
85
95
 
@@ -90,7 +100,7 @@ module StandardId
90
100
  raise StandardId::OAuthError, e.message, cause: e
91
101
  end
92
102
 
93
- def verify_id_token(id_token:)
103
+ def verify_id_token(id_token:, nonce: nil)
94
104
  raise StandardId::InvalidRequestError, "Missing id_token" if id_token.blank?
95
105
 
96
106
  response = HttpClient.post_form(TOKEN_INFO_ENDPOINT, id_token: id_token)
@@ -99,6 +109,15 @@ module StandardId
99
109
 
100
110
  token_info = JSON.parse(response.body)
101
111
 
112
+ # Validate nonce if provided (web flow with server-generated nonce)
113
+ if nonce.present?
114
+ token_nonce = token_info["nonce"]
115
+ if token_nonce != nonce
116
+ raise StandardId::InvalidRequestError,
117
+ "ID token nonce mismatch. Expected: #{nonce}, got: #{token_nonce}"
118
+ end
119
+ end
120
+
102
121
  unless token_info["aud"] == credentials[:client_id]
103
122
  raise StandardId::InvalidRequestError,
104
123
  "ID token audience mismatch. Expected: #{credentials[:client_id]}, got: #{token_info["aud"]}"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StandardId
4
4
  module Google
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_id-google
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim