Fix: Add UserFactorSignedNonce Schema and Discriminator Mapping for Okta FastPass#551
Merged
Conversation
…kta FastPass factor
This commit fixes deserialization failures in `list_factors()` (GET `/api/v1/users/{userId}/factors`) for users enrolled with the Okta FastPass `signed_nonce` factor type.
Problem:
- `UserApi.list_factors()` raised a Pydantic ValidationError whenever the response contained a factor with `factorType: "signed_nonce"`.
- The `UserFactorType` enum already included `signed_nonce`, but the `UserFactor` schema in the OpenAPI spec lacked both a discriminator mapping for it and a dedicated `UserFactorSignedNonce` subschema.
- As a result, the generated SDK had no class to route the payload to, and discriminator validation rejected the response: Discriminator property name: factorType, mapping: {"call": ..., "email": ..., "push": ..., "question": ..., "sms": ..., "token": ..., "token:hardware": ..., "token:hotp": ..., "token:software:totp": ..., "u2f": ..., "web": ..., "webauthn": ...} (no entry for `signed_nonce`).
Root Cause:
- Missing `signed_nonce: '#/components/schemas/UserFactorSignedNonce'` entry under `UserFactor.discriminator.mapping` in `openapi/api.yaml`.
- Missing `UserFactorSignedNonce`, `UserFactorSignedNonceProfile`, and `UserFactorSignedNonceProfileKey` schema definitions describing the Okta FastPass payload (device metadata + JWK-style key material).
Solution:
1. Added `signed_nonce` to the `UserFactor` discriminator mapping in `openapi/api.yaml`.
2. Defined three new component schemas in `openapi/api.yaml`:
- `UserFactorSignedNonce` (allOf `UserFactor` + `provider: OKTA`, `factorType: signed_nonce`, `profile`)
- `UserFactorSignedNonceProfile` (device metadata: `credentialId`, `deviceType`, `name`, `platform`, `version`, `keys[]`)
- `UserFactorSignedNonceProfileKey` (JWK fields: `kty`, `use`, `kid`, `jwkType`, `e`, `n`, `crv`, `x`, `y`, `x5c`)
3. Regenerated model files:
- `okta/models/user_factor_signed_nonce.py`
- `okta/models/user_factor_signed_nonce_profile.py`
- `okta/models/user_factor_signed_nonce_profile_key.py`
4. Updated `okta/models/user_factor.py` to register the new subclass in the discriminator map, the `oneOf` union, and `from_dict()` routing.
5. Wired the new models into the lazy-import maps in `okta/__init__.py` and `okta/models/__init__.py` (added to the `user_factor` model group).
6. Added API docs: `docs/UserFactorSignedNonce.md`, `docs/UserFactorSignedNonceProfile.md`, `docs/UserFactorSignedNonceProfileKey.md`.
Testing:
- Verified against a live Okta org with a user enrolled in Okta FastPass (`signed_nonce`). `client.list_factors(user_id)` now returns the full factor list, including `UserFactorSignedNonce` instances correctly deserialized with populated `profile` and `profile.keys` fields.
- No breaking changes to existing factor types; all other discriminator mappings remain intact.
Related:
- #311
- #387
Fixes: OKTA-1152856
Address reviewer feedback on the OKTA-1152856 PR (signed_nonce UserFactor regression test). Blocker ------- * tests/integration/cassettes/test_factors_it/TestFactorsResource.test_list_factors_signed_nonce.yaml re-recorded with fully fictional placeholders: Minor ----- * okta/models/__init__.py: switched the new "UserFactorSignedNonce" entry in the `user_factor` group from double quotes to single quotes to match every surrounding entry. * Confirmed _MODEL_GROUPS consistency: profile-level user-factor models (UserFactorSMSProfile, UserFactorPushProfile, UserFactorEmailProfile, UserFactorWebAuthnProfile, UserFactorTokenProfile, …) are intentionally registered only in the flat _MODEL_MODULE_MAP and are not added to _MODEL_GROUPS. The new UserFactorSignedNonceProfile and UserFactorSignedNonceProfileKey already follow that same pattern, so no change was required. Testing ------- * pytest tests/integration/test_factors_it.py::TestFactorsResource::test_list_factors_signed_nonce passes in replay mode against the sanitized cassette, confirming the signed_nonce discriminator routing and nested profile/key deserialization are still validated.
manmohan-shaw-okta
approved these changes
Jun 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix: Add
UserFactorSignedNonceSchema and Discriminator Mapping for Okta FastPassSummary
Adds the missing
signed_noncediscriminator branch (Okta FastPass) to theUserFactorpolymorphic model, along with three new component schemas (UserFactorSignedNonce,UserFactorSignedNonceProfile,UserFactorSignedNonceProfileKey). This unblockslist_factors()for any user enrolled in Okta FastPass.Ticket: OKTA-1152856
Problem
UserApi.list_factors()(GET/api/v1/users/{userId}/factors) failed with a PydanticValidationErrorwhenever the response contained a factor withfactorType: "signed_nonce":There is no entry for
signed_nonce, so the SDK has nothing to deserialize the payload into. TheUserFactorTypeenum already advertisessigned_nonceas a valid value, but the corresponding subschema and discriminator mapping were never added to the OAS spec.Affected endpoint:
UserApi.list_factors(user_id)Root Cause
UserFactor.discriminator.mappinginopenapi/api.yamlhad nosigned_nonce→ schema mapping.UserFactorSignedNonce(or supportingUserFactorSignedNonceProfile/UserFactorSignedNonceProfileKey) schema existed to describe the Okta FastPass payload (device metadata + JWK-style key material returned for each enrolled device).The OAS3 spec issue has been fixed upstream; this PR mirrors that fix in the SDK spec snapshot (
openapi/api.yaml) and regenerates the affected models.Solution
Spec changes —
openapi/api.yamlAdded the discriminator mapping:
Added three new component schemas:
UserFactorSignedNonce—allOfextension ofUserFactor, pinningfactorType: signed_nonce,provider: OKTA, and aprofilereference. Documents that enrollment/activation must go through the Okta Verify authenticator flow; the Factors API only supports list/delete for this type.UserFactorSignedNonceProfile— Device metadata for an Okta FastPass registration:credentialId,deviceType,name,platform,version, and akeys[]array.UserFactorSignedNonceProfileKey— JWK-style key entry:kty(RSA/EC),use,kid,jwkType(proofOfPossession/userVerification/userVerificationBioOrPin), RSA fields (e,n), EC fields (crv,x,y), andx5c.Generated model changes
okta/models/user_factor_signed_nonce.py(new, generated)okta/models/user_factor_signed_nonce_profile.py(new, generated)okta/models/user_factor_signed_nonce_profile_key.py(new, generated)Discriminator routing —
okta/models/user_factor.pyUserFactorSignedNoncein:__discriminator_value_class_map("signed_nonce": "UserFactorSignedNonce").Union[...]of concrete subclasses returned byfrom_json/from_dict.from_dict()dispatch chain (with the same self-recursion guard used for the other variants).TYPE_CHECKINGimport block.Lazy-import wiring
okta/__init__.py— Added the three new classes to_LAZY_IMPORT_MAPso they can be imported asfrom okta import UserFactorSignedNonce.okta/models/__init__.py— Added the same entries to the model-level_LAZY_IMPORT_MAPand addedUserFactorSignedNonceto theuser_factormodel group in_MODEL_GROUPS.Docs
docs/UserFactorSignedNonce.md(new)docs/UserFactorSignedNonceProfile.md(new)docs/UserFactorSignedNonceProfileKey.md(new)Files Changed
openapi/api.yamlokta/models/user_factor.pyokta/models/user_factor_signed_nonce.pyokta/models/user_factor_signed_nonce_profile.pyokta/models/user_factor_signed_nonce_profile_key.pyokta/__init__.pyokta/models/__init__.pyuser_factorgroup entrydocs/UserFactorSignedNonce.mddocs/UserFactorSignedNonceProfile.mddocs/UserFactorSignedNonceProfileKey.mdTesting
signed_nonce):client.list_factors(user_id)returns the complete factor list with no validation errors.UserFactorSignedNonceinstances deserialize correctly, with populatedprofile.deviceType,profile.platform,profile.version, andprofile.keys[](including both RSA and EC keys,proofOfPossessionanduserVerificationvariants).push,webauthn,token:software:totp, etc.) — the discriminator map still routes all existing values to their original subclasses.from okta import UserFactorSignedNonceresolves through_LAZY_IMPORT_MAPwithout eagerly loading all 1000+ models.Backward Compatibility
ValidationErrorfromlist_factors()for FastPass users can now remove that workaround.Related
Upstream
The OAS3 spec issue has been fixed in
specs/monolith/management/spec/v1/am/factors.yaml. Once that fix propagates to the canonicalapi.yamlsource, regenerating the SDK from the upstream spec should produce equivalent output to this PR.