standard_id-apple 0.7.0 → 0.8.0

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: 9a3e07c309c99df7cef0af887b983707a504e9c68966899bb9f5a173e7fa6e45
4
- data.tar.gz: fe8506b6efa0b7fad211136546e04f318057bcd53bb4961d61dcfd6352ec0118
3
+ metadata.gz: c5bb7427c0d928fdf21192e6a48c70431a51a2e3c05d3b96dd192eab760247f0
4
+ data.tar.gz: ef547da4fd6b44facb4cafca70b80a98b65cec3859a6715b1c72160a51769aa1
5
5
  SHA512:
6
- metadata.gz: 723d58e82c79eb36a8c963e9f94e68718df906af0f498831e6095bc8ec7774f806a0ab4e8661942401654d055bf42fa96633a7ddca0d38d8f719721bb0a8419a
7
- data.tar.gz: ca5b3cdd9a2a6ce0ab8dfb47dfc9291442d3f62e9149c00630088da543d512d0537540b92ee488159ef69d2d02ee39781075ff0ab03b6c998448afb881afdc74
6
+ metadata.gz: f49035c18090743a5513da37b8eb3e66eef03a430a050aa8643492e668f8cff2cb26d7b0247a21b0854ea086023115b0b62a2fc85f613bb965f8dacecf8335ea
7
+ data.tar.gz: 4e23288305fcf8b7dc59fd203ad62b40c3f85dd0983b931a0c948a4d1c75c3712905e46efa4f4c63ae2e37eec3bae7317e7ca09e92aa17564035c21dcb5a47d2
data/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-10-08
11
+
12
+ ### Added
13
+
14
+ - **`apple_redirect_uri`** (optional, ENV fallback `APPLE_REDIRECT_URI`): the
15
+ return URL web-flow codes are authorized against when the caller exchanges
16
+ them without one — normally `https://<host>/auth/callback_mobile/apple`,
17
+ registered on the Services ID.
18
+
19
+ ### Fixed
20
+
21
+ - **`flow=web` code exchanges through the API callback** (e.g. Android signing
22
+ in through Apple's web flow) sent no `redirect_uri` to Apple's token
23
+ endpoint, so Apple rejected them with `invalid_grant`. `resolve_params` now
24
+ fills the configured `apple_redirect_uri` for the web flow when the caller
25
+ passed none. A caller's `redirect_uri` (the browser callback's) still wins,
26
+ and the native flow is unchanged. No-op until `apple_redirect_uri` is set.
27
+
10
28
  ## [0.7.0] - 2026-10-06
11
29
 
12
30
  ### Added
data/README.md CHANGED
@@ -45,6 +45,7 @@ configuration block, in the `social` scope:
45
45
  StandardId.configure do |config|
46
46
  config.social.apple_client_id = ENV["APPLE_CLIENT_ID"]
47
47
  config.social.apple_mobile_client_id = ENV["APPLE_MOBILE_CLIENT_ID"] # optional
48
+ config.social.apple_redirect_uri = ENV["APPLE_REDIRECT_URI"] # optional, see Flows
48
49
  config.social.apple_team_id = ENV["APPLE_TEAM_ID"]
49
50
  config.social.apple_key_id = ENV["APPLE_KEY_ID"]
50
51
  config.social.apple_private_key = ENV["APPLE_PRIVATE_KEY"]
@@ -62,6 +63,7 @@ variable named after it, upper-cased:
62
63
  |---|---|
63
64
  | `apple_client_id` | `APPLE_CLIENT_ID` |
64
65
  | `apple_mobile_client_id` | `APPLE_MOBILE_CLIENT_ID` |
66
+ | `apple_redirect_uri` | `APPLE_REDIRECT_URI` |
65
67
  | `apple_team_id` | `APPLE_TEAM_ID` |
66
68
  | `apple_key_id` | `APPLE_KEY_ID` |
67
69
  | `apple_private_key` | `APPLE_PRIVATE_KEY` (deprecated fallback: `APPLE_PRIVATE_KEY_PEM`) |
@@ -103,6 +105,14 @@ UI while its native sign-in keeps working.
103
105
  | Native `id_token` (`/api/oauth/callback/apple`) | `apple_mobile_client_id` | no |
104
106
  | Web flow on Android etc. (`flow=web` on the API callback, redirects back into the app) | `apple_client_id` | yes |
105
107
 
108
+ Apple only exchanges a web-flow code when the token request carries the same
109
+ `redirect_uri` the authorization used. The browser callback passes its own
110
+ URL. The API callback passes none, so for `flow=web` there set
111
+ `apple_redirect_uri` to the return URL those codes are authorized against,
112
+ normally the mobile bounce `https://<host>/auth/callback_mobile/apple`
113
+ (registered on the Services ID). A `redirect_uri` the caller passes always
114
+ wins, and the native flow never sends one.
115
+
106
116
  Apple's signing keys (JWKS) are fetched through `StandardId::HttpClient`
107
117
  (timeouts, private-address guard) and cached in-process for an hour; a token
108
118
  signed with an unknown key triggers one early refetch, at most once a minute.
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Apple Sign In credentials for StandardId.
4
4
  #
5
- # These five fields are declared by the standard_id-apple gem, NOT by
5
+ # These six fields are declared by the standard_id-apple gem, NOT by
6
6
  # standard_id — they do not exist unless this gem is in the Gemfile, on any
7
7
  # standard_id version. Removing the gem and leaving this file behind raises
8
8
  # `StandardId::ConfigurationError: Unknown field 'apple_client_id' for scope
@@ -15,10 +15,10 @@
15
15
  #
16
16
  # ENV: on standard_id >= 0.42 a field this file does not assign falls back to
17
17
  # the ENV variable named after it, upper-cased — APPLE_CLIENT_ID,
18
- # APPLE_MOBILE_CLIENT_ID, APPLE_TEAM_ID, APPLE_KEY_ID, APPLE_PRIVATE_KEY — which
19
- # are the names used below. The assignments are kept so the wiring is visible;
20
- # delete any you prefer to leave to the fallback, or point one at another
21
- # source (e.g. Rails credentials).
18
+ # APPLE_MOBILE_CLIENT_ID, APPLE_REDIRECT_URI, APPLE_TEAM_ID, APPLE_KEY_ID,
19
+ # APPLE_PRIVATE_KEY — which are the names used below. The assignments are kept
20
+ # so the wiring is visible; delete any you prefer to leave to the fallback, or
21
+ # point one at another source (e.g. Rails credentials).
22
22
  #
23
23
  # apple_client_id switches the web flow on. While it is set, StandardId's boot
24
24
  # check requires apple_team_id, apple_key_id and apple_private_key (warns by
@@ -32,6 +32,12 @@ StandardId.configure do |config|
32
32
  # on a web-only app is correct — don't point it at APPLE_CLIENT_ID.
33
33
  config.social.apple_mobile_client_id = ENV["APPLE_MOBILE_CLIENT_ID"]
34
34
 
35
+ # Optional: the return URL web-flow codes exchanged through the API callback
36
+ # (`flow=web`, e.g. Android) were authorized against — normally
37
+ # https://<host>/auth/callback_mobile/apple, registered on the Services ID.
38
+ # Apple rejects the exchange (invalid_grant) without it.
39
+ config.social.apple_redirect_uri = ENV["APPLE_REDIRECT_URI"]
40
+
35
41
  config.social.apple_team_id = ENV["APPLE_TEAM_ID"]
36
42
  config.social.apple_key_id = ENV["APPLE_KEY_ID"]
37
43
 
@@ -85,11 +85,16 @@ module StandardId
85
85
  #
86
86
  # ENV fallbacks (standard_id >= 0.42) use the upper-cased field names:
87
87
  # APPLE_CLIENT_ID, APPLE_MOBILE_CLIENT_ID, APPLE_PRIVATE_KEY,
88
- # APPLE_KEY_ID, APPLE_TEAM_ID.
88
+ # APPLE_KEY_ID, APPLE_TEAM_ID, APPLE_REDIRECT_URI.
89
+ #
90
+ # `apple_redirect_uri` is optional: the return URL a web-flow code
91
+ # was issued against, for callers that exchange the code without
92
+ # passing one (see #resolve_params).
89
93
  def config_schema
90
94
  {
91
95
  apple_client_id: { type: :string, default: nil },
92
96
  apple_mobile_client_id: { type: :string, default: nil },
97
+ apple_redirect_uri: { type: :string, default: nil },
93
98
  apple_private_key: { type: :string, default: -> { legacy_private_key_from_env }, required: true },
94
99
  apple_key_id: { type: :string, default: nil, required: true },
95
100
  apple_team_id: { type: :string, default: nil, required: true }
@@ -113,11 +118,24 @@ module StandardId
113
118
 
114
119
  # The web flow authenticates against the Services ID, the native flow
115
120
  # against the app's bundle ID.
121
+ #
122
+ # A web-flow code must be exchanged with the redirect_uri it was
123
+ # authorized against, or Apple answers invalid_grant. The browser
124
+ # callback passes its own URL. The API callback (`flow=web`, e.g.
125
+ # Android signing in through Apple's web flow and bouncing back via
126
+ # `/auth/callback_mobile/apple`) passes none, so the configured
127
+ # `apple_redirect_uri` fills the gap. A redirect_uri the caller did
128
+ # pass always wins; the native flow never gets one.
116
129
  def resolve_params(params, context: {})
117
130
  flow = context[:flow] || :web
118
- client_id = flow == :mobile ? StandardId.config.apple_mobile_client_id : StandardId.config.apple_client_id
131
+ return params.merge(client_id: StandardId.config.apple_mobile_client_id) if flow == :mobile
119
132
 
120
- params.merge(client_id: client_id)
133
+ resolved = params.merge(client_id: StandardId.config.apple_client_id)
134
+ configured_redirect_uri = StandardId.config.apple_redirect_uri
135
+ if resolved[:redirect_uri].blank? && configured_redirect_uri.present?
136
+ resolved[:redirect_uri] = configured_redirect_uri
137
+ end
138
+ resolved
121
139
  end
122
140
 
123
141
  def exchange_code_for_user_info(code:, redirect_uri:, client_id: StandardId.config.apple_client_id, nonce: nil)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StandardId
4
4
  module Apple
5
- VERSION = "0.7.0"
5
+ VERSION = "0.8.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_id-apple
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim