standard_id-apple 0.1.0 → 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/README.md +5 -5
- data/lib/standard_id/apple/providers/apple.rb +41 -25
- data/lib/standard_id/apple/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: 3c67afb888da7a7a20542d46149eaf86215d02de9c097a757b98223f4c992811
|
|
4
|
+
data.tar.gz: b905edd16718f7fee932b567250a7caa5136d1cf64f82030668b7bcb8dd40d87
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fdd51606e4a08272c10bae448cecbf0472e8449a5679229013138b5fed0abef6f2e404ef1ef495731a1744c58538fbfc1c141af974ad02c825212a5a51adffc1
|
|
7
|
+
data.tar.gz: 58e35ebc3804fd52719774db5a63229918e0c89462dca2b7f654a8a57820389549044c1ca42e27cf46155afc6617942084e1ce41b650866c9ba0a049e7599289
|
data/README.md
CHANGED
|
@@ -26,11 +26,11 @@ Configure Apple credentials via the StandardId configuration block:
|
|
|
26
26
|
|
|
27
27
|
```ruby
|
|
28
28
|
StandardId.configure do |config|
|
|
29
|
-
config.
|
|
30
|
-
config.
|
|
31
|
-
config.
|
|
32
|
-
config.
|
|
33
|
-
config.
|
|
29
|
+
config.apple_client_id = ENV["APPLE_CLIENT_ID"]
|
|
30
|
+
config.apple_mobile_client_id = ENV["APPLE_MOBILE_CLIENT_ID"] # optional
|
|
31
|
+
config.apple_team_id = ENV["APPLE_TEAM_ID"]
|
|
32
|
+
config.apple_key_id = ENV["APPLE_KEY_ID"]
|
|
33
|
+
config.apple_private_key = ENV["APPLE_PRIVATE_KEY_PEM"]
|
|
34
34
|
end
|
|
35
35
|
```
|
|
36
36
|
|
|
@@ -12,40 +12,47 @@ module StandardId
|
|
|
12
12
|
JWKS_URI = "#{ISSUER}/auth/keys".freeze
|
|
13
13
|
DEFAULT_SCOPE = "name email".freeze
|
|
14
14
|
DEFAULT_RESPONSE_MODE = "form_post".freeze
|
|
15
|
+
AUTHORIZATION_PARAM_DEFAULTS = {
|
|
16
|
+
scope: DEFAULT_SCOPE,
|
|
17
|
+
response_mode: DEFAULT_RESPONSE_MODE
|
|
18
|
+
}.freeze
|
|
15
19
|
|
|
16
20
|
class << self
|
|
17
21
|
def provider_name
|
|
18
22
|
"apple"
|
|
19
23
|
end
|
|
20
24
|
|
|
21
|
-
def
|
|
22
|
-
scope
|
|
23
|
-
|
|
25
|
+
def supported_authorization_params
|
|
26
|
+
[:nonce, :scope, :response_mode]
|
|
27
|
+
end
|
|
24
28
|
|
|
29
|
+
def authorization_url(state:, redirect_uri:, **options)
|
|
25
30
|
ensure_basic_credentials!
|
|
26
31
|
|
|
27
32
|
query = {
|
|
28
|
-
client_id: StandardId.config.
|
|
29
|
-
redirect_uri
|
|
33
|
+
client_id: StandardId.config.apple_client_id,
|
|
34
|
+
redirect_uri:,
|
|
30
35
|
response_type: "code",
|
|
31
|
-
|
|
32
|
-
response_mode: response_mode,
|
|
33
|
-
state: state
|
|
36
|
+
state:
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
supported_authorization_params.each do |param|
|
|
40
|
+
query[param] = options[param] || AUTHORIZATION_PARAM_DEFAULTS[param]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
"#{AUTH_ENDPOINT}?#{URI.encode_www_form(query.compact)}"
|
|
37
44
|
end
|
|
38
45
|
|
|
39
|
-
def get_user_info(code: nil, id_token: nil, access_token: nil, redirect_uri: nil, **options)
|
|
40
|
-
client_id = options[:client_id] || StandardId.config.
|
|
46
|
+
def get_user_info(code: nil, id_token: nil, access_token: nil, redirect_uri: nil, nonce: nil, **options)
|
|
47
|
+
client_id = options[:client_id] || StandardId.config.apple_client_id
|
|
41
48
|
|
|
42
49
|
if id_token.present?
|
|
43
50
|
build_response(
|
|
44
|
-
verify_id_token(id_token: id_token, client_id: client_id),
|
|
51
|
+
verify_id_token(id_token: id_token, client_id: client_id, nonce: nonce),
|
|
45
52
|
tokens: { id_token: id_token }
|
|
46
53
|
)
|
|
47
54
|
elsif code.present?
|
|
48
|
-
exchange_code_for_user_info(code: code, redirect_uri: redirect_uri, client_id: client_id)
|
|
55
|
+
exchange_code_for_user_info(code: code, redirect_uri: redirect_uri, client_id: client_id, nonce: nonce)
|
|
49
56
|
elsif access_token.present?
|
|
50
57
|
raise StandardId::InvalidRequestError, "Access token login flow is not supported for Apple"
|
|
51
58
|
else
|
|
@@ -77,12 +84,12 @@ module StandardId
|
|
|
77
84
|
|
|
78
85
|
def resolve_params(params, context: {})
|
|
79
86
|
flow = context[:flow] || :web
|
|
80
|
-
client_id = flow == :mobile ? StandardId.config.
|
|
87
|
+
client_id = flow == :mobile ? StandardId.config.apple_mobile_client_id : StandardId.config.apple_client_id
|
|
81
88
|
|
|
82
89
|
params.merge(client_id: client_id)
|
|
83
90
|
end
|
|
84
91
|
|
|
85
|
-
def exchange_code_for_user_info(code:, redirect_uri:, client_id: StandardId.config.
|
|
92
|
+
def exchange_code_for_user_info(code:, redirect_uri:, client_id: StandardId.config.apple_client_id, nonce: nil)
|
|
86
93
|
ensure_full_credentials!(client_id: client_id)
|
|
87
94
|
raise StandardId::InvalidRequestError, "Missing authorization code" if code.blank?
|
|
88
95
|
|
|
@@ -108,7 +115,7 @@ module StandardId
|
|
|
108
115
|
raise StandardId::InvalidRequestError, "Apple response missing id_token" if id_token.blank?
|
|
109
116
|
|
|
110
117
|
tokens = extract_token_payload(parsed_token)
|
|
111
|
-
user_info = verify_id_token(id_token: id_token, client_id: client_id)
|
|
118
|
+
user_info = verify_id_token(id_token: id_token, client_id: client_id, nonce: nonce)
|
|
112
119
|
|
|
113
120
|
build_response(user_info, tokens: tokens)
|
|
114
121
|
rescue StandardError => e
|
|
@@ -117,7 +124,7 @@ module StandardId
|
|
|
117
124
|
raise StandardId::OAuthError, e.message, cause: e
|
|
118
125
|
end
|
|
119
126
|
|
|
120
|
-
def verify_id_token(id_token:, client_id: StandardId.config.
|
|
127
|
+
def verify_id_token(id_token:, client_id: StandardId.config.apple_client_id, nonce: nil)
|
|
121
128
|
raise StandardId::InvalidRequestError, "Missing id_token" if id_token.blank?
|
|
122
129
|
raise StandardId::InvalidRequestError, "Apple client_id is not configured" if client_id.blank?
|
|
123
130
|
|
|
@@ -137,6 +144,15 @@ module StandardId
|
|
|
137
144
|
verify_aud: true
|
|
138
145
|
)
|
|
139
146
|
|
|
147
|
+
# Validate nonce if provided (web flow with server-generated nonce)
|
|
148
|
+
if nonce.present?
|
|
149
|
+
token_nonce = verified_payload["nonce"]
|
|
150
|
+
if token_nonce != nonce
|
|
151
|
+
raise StandardId::InvalidRequestError,
|
|
152
|
+
"ID token nonce mismatch. Expected: #{nonce}, got: #{token_nonce}"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
140
156
|
{
|
|
141
157
|
"sub" => verified_payload["sub"],
|
|
142
158
|
"email" => verified_payload["email"],
|
|
@@ -155,7 +171,7 @@ module StandardId
|
|
|
155
171
|
|
|
156
172
|
private
|
|
157
173
|
|
|
158
|
-
def ensure_basic_credentials!(client_id: StandardId.config.
|
|
174
|
+
def ensure_basic_credentials!(client_id: StandardId.config.apple_client_id)
|
|
159
175
|
return if client_id.present?
|
|
160
176
|
|
|
161
177
|
raise StandardId::InvalidRequestError, "Apple OAuth is not configured"
|
|
@@ -165,9 +181,9 @@ module StandardId
|
|
|
165
181
|
ensure_basic_credentials!(client_id: client_id)
|
|
166
182
|
|
|
167
183
|
required = [
|
|
168
|
-
StandardId.config.
|
|
169
|
-
StandardId.config.
|
|
170
|
-
StandardId.config.
|
|
184
|
+
StandardId.config.apple_private_key,
|
|
185
|
+
StandardId.config.apple_key_id,
|
|
186
|
+
StandardId.config.apple_team_id
|
|
171
187
|
]
|
|
172
188
|
|
|
173
189
|
return unless required.any?(&:blank?)
|
|
@@ -175,21 +191,21 @@ module StandardId
|
|
|
175
191
|
raise StandardId::InvalidRequestError, "Apple OAuth credentials are incomplete"
|
|
176
192
|
end
|
|
177
193
|
|
|
178
|
-
def generate_client_secret(client_id: StandardId.config.
|
|
194
|
+
def generate_client_secret(client_id: StandardId.config.apple_client_id)
|
|
179
195
|
header = {
|
|
180
196
|
alg: "ES256",
|
|
181
|
-
kid: StandardId.config.
|
|
197
|
+
kid: StandardId.config.apple_key_id
|
|
182
198
|
}
|
|
183
199
|
|
|
184
200
|
payload = {
|
|
185
|
-
iss: StandardId.config.
|
|
201
|
+
iss: StandardId.config.apple_team_id,
|
|
186
202
|
iat: Time.current.to_i,
|
|
187
203
|
exp: Time.current.to_i + 3600,
|
|
188
204
|
aud: ISSUER,
|
|
189
205
|
sub: client_id
|
|
190
206
|
}
|
|
191
207
|
|
|
192
|
-
private_key = OpenSSL::PKey::EC.new(StandardId.config.
|
|
208
|
+
private_key = OpenSSL::PKey::EC.new(StandardId.config.apple_private_key)
|
|
193
209
|
JWT.encode(payload, private_key, "ES256", header)
|
|
194
210
|
end
|
|
195
211
|
|