Describe the bug?
In okta-mobile-swift 2.1.5, calling Remediation.proceed() on any IDX remediation whose form contains a named field with multiple nested children serializes only the last child — sibling fields are silently dropped. This breaks the standard authenticator-verification-data and select-authenticator-authenticate flows because the immutable required id field gets clobbered, and the Okta server rejects the request with HTTP 400.
Pinning back to 2.1.4 resolves the issue.
Root cause
The regression is in Sources/OktaIdxAuth/Internal/Extensions/IDXRemediation+Extensions.swift, where
Remediation.Form.Field.formValue was rewritten between 2.1.4 and 2.1.5.
2.1.4 (correct) accumulates all child fields into a dict, then wraps under the parent name:
let childValues: [String: any JSONRepresentable] = try form.allFields.reduce(into: [:]) { (result, formValue) in
let nestedResult = try formValue.formValues()
...
if let name = formValue.name {
result[name] = nestedResult
}
...
}
result = try JSON([name: childValues])
2.1.5 (broken) overwrites the same key on every loop iteration:
if let form = self.form, !form.allFields.isEmpty {
for field in form.allFields {
guard let nestedResult = try field.formValue else { continue }
json[name] = nestedResult // ← overwrites previous sibling each pass
}
}
Walking the loop for an authenticator-verification-data form:
- field = id → nestedResult = {"id": ""} → json["authenticator"] = {"id": "..."}
- field = methodType (with selectedOption = "email") → nestedResult = {"methodType": "email"} → json["authenticator"] = {"methodType": "email"} ← clobbers the previous entry
The body that reaches the server contains only methodType. The fix is to merge nested results under the parent key instead of overwriting (mirroring the reduce(into:) accumulation in 2.1.4).
Impact
This breaks Remediation.proceed() for any IDX remediation that has a named field with two or more nested children — i.e. essentially every multi-factor challenge flow (authenticator-verification-data, select-authenticator-authenticate, challenge-authenticator for factors that send id+methodType, etc.).
We caught it via Proxyman captures: identical Okta server responses produce different request bodies between a 2.1.4-linked binary (works) and a 2.1.5-linked binary (400s on every challenge attempt).
Workaround
Pin to exactVersion 2.1.4 until this is fixed.
What is expected to happen?
Calling Remediation.proceed() on an authenticator-verification-data remediation (with nested authenticator form containing id + methodType siblings) should serialize all sibling fields under the parent key, producing:
{
"authenticator": { "id": "<authenticator-id>", "methodType": "email" },
"stateHandle": "<state-handle>"
}
Okta returns 200 OK and the flow progresses to challenge-authenticator. This is the 2.1.4 behavior.
What is the actual behavior?
Only the last sibling field is serialized — earlier siblings are silently overwritten in the JSON dictionary. The actual request body is:
{
"stateHandle": "<state-handle>",
"authenticator": { "methodType": "email" }
}
Okta returns 400 Bad Request because id is required and immutable in the form schema. The flow is unrecoverable from the client side, breaking every multi-factor challenge.
Reproduction Steps?
- Start an Interaction Code Flow against an Okta tenant where the user has an email authenticator enrollment.
- Drive the flow through
interact → introspect → identify so the response contains an authenticator-verification-data remediation with the standard nested form:
{
"name": "authenticator-verification-data",
"value": [
{
"name": "authenticator",
"form": {
"value": [
{ "name": "id", "required": true, "value": "<authenticator-id>", "mutable": false },
{ "name": "methodType", "type": "string", "required": true, "options": [{ "label": "Email", "value": "email"
}] }
]
}
},
{ "name": "stateHandle", "required": true, "value": "<state-handle>", "visible": false, "mutable": false }
]
}
- Set methodTypeField.selectedOption to the email option and call remediation.proceed().
- Capture the outgoing HTTP request (e.g. via Proxyman). Observe that the authenticator object is missing the id field, and the server responds with HTTP 400.
- Repeat with the dependency pinned to exactVersion 2.1.4 — the request body now contains both id and methodType, and the server returns 200 OK.
Additional Information?
No response
SDK Version(s)
2.1.5 (regression) — last working version: 2.1.4
Product: OktaIdxAuth
Platform: iOS (SPM)
Build Information
No response
Describe the bug?
In
okta-mobile-swift2.1.5, callingRemediation.proceed()on any IDX remediation whose form contains a named field with multiple nested children serializes only the last child — sibling fields are silently dropped. This breaks the standardauthenticator-verification-dataandselect-authenticator-authenticateflows because the immutable requiredidfield gets clobbered, and the Okta server rejects the request with HTTP 400.Pinning back to 2.1.4 resolves the issue.
Root cause
The regression is in
Sources/OktaIdxAuth/Internal/Extensions/IDXRemediation+Extensions.swift, whereRemediation.Form.Field.formValuewas rewritten between 2.1.4 and 2.1.5.2.1.4 (correct) accumulates all child fields into a dict, then wraps under the parent name:
2.1.5 (broken) overwrites the same key on every loop iteration:
Walking the loop for an authenticator-verification-data form:
The body that reaches the server contains only methodType. The fix is to merge nested results under the parent key instead of overwriting (mirroring the reduce(into:) accumulation in 2.1.4).
Impact
This breaks Remediation.proceed() for any IDX remediation that has a named field with two or more nested children — i.e. essentially every multi-factor challenge flow (authenticator-verification-data, select-authenticator-authenticate, challenge-authenticator for factors that send id+methodType, etc.).
We caught it via Proxyman captures: identical Okta server responses produce different request bodies between a 2.1.4-linked binary (works) and a 2.1.5-linked binary (400s on every challenge attempt).
Workaround
Pin to exactVersion 2.1.4 until this is fixed.
What is expected to happen?
Calling
Remediation.proceed()on anauthenticator-verification-dataremediation (with nestedauthenticatorform containingid+methodTypesiblings) should serialize all sibling fields under the parent key, producing:{ "authenticator": { "id": "<authenticator-id>", "methodType": "email" }, "stateHandle": "<state-handle>" }Okta returns 200 OK and the flow progresses to challenge-authenticator. This is the 2.1.4 behavior.
What is the actual behavior?
Only the last sibling field is serialized — earlier siblings are silently overwritten in the JSON dictionary. The actual request body is:
{ "stateHandle": "<state-handle>", "authenticator": { "methodType": "email" } }Okta returns 400 Bad Request because id is required and immutable in the form schema. The flow is unrecoverable from the client side, breaking every multi-factor challenge.
Reproduction Steps?
interact→introspect→identifyso the response contains anauthenticator-verification-dataremediation with the standard nested form:{ "name": "authenticator-verification-data", "value": [ { "name": "authenticator", "form": { "value": [ { "name": "id", "required": true, "value": "<authenticator-id>", "mutable": false }, { "name": "methodType", "type": "string", "required": true, "options": [{ "label": "Email", "value": "email" }] } ] } }, { "name": "stateHandle", "required": true, "value": "<state-handle>", "visible": false, "mutable": false } ] }Additional Information?
No response
SDK Version(s)
2.1.5 (regression) — last working version: 2.1.4
Product: OktaIdxAuth
Platform: iOS (SPM)
Build Information
No response