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 +4 -4
- data/lib/standard_id/google/providers/google.rb +30 -11
- data/lib/standard_id/google/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 54193c55f7d0359d865c030e3c4f42457d038a53bff4a3b3e78e46f792d577a2
|
|
4
|
+
data.tar.gz: 34ba47a877d286cf4cb8f5662e08665c78b03e6025c70b2490c87d536de27064
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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"]}"
|