Skip to content

Fix: Add UserFactorSignedNonce Schema and Discriminator Mapping for Okta FastPass#551

Open
BinoyOza-okta wants to merge 2 commits into
masterfrom
OKTA-1152856
Open

Fix: Add UserFactorSignedNonce Schema and Discriminator Mapping for Okta FastPass#551
BinoyOza-okta wants to merge 2 commits into
masterfrom
OKTA-1152856

Conversation

@BinoyOza-okta

Copy link
Copy Markdown
Contributor

Fix: Add UserFactorSignedNonce Schema and Discriminator Mapping for Okta FastPass

Summary

Adds the missing signed_nonce discriminator branch (Okta FastPass) to the UserFactor polymorphic model, along with three new component schemas (UserFactorSignedNonce, UserFactorSignedNonceProfile, UserFactorSignedNonceProfileKey). This unblocks list_factors() for any user enrolled in Okta FastPass.

Ticket: OKTA-1152856

Problem

UserApi.list_factors() (GET /api/v1/users/{userId}/factors) failed with a Pydantic ValidationError whenever the response contained a factor with factorType: "signed_nonce":

Discriminator property name: factorType, mapping: {
  "call": "UserFactorCall", "email": "UserFactorEmail",
  "push": "UserFactorPush", "question": "UserFactorSecurityQuestion",
  "sms": "UserFactorSMS", "token": "UserFactorToken",
  "token:hardware": "UserFactorTokenHardware",
  "token:hotp": "UserFactorTokenHOTP",
  "token:software:totp": "UserFactorTokenSoftwareTOTP",
  "u2f": "UserFactorU2F", "web": "UserFactorWeb",
  "webauthn": "UserFactorWebAuthn"
}

There is no entry for signed_nonce, so the SDK has nothing to deserialize the payload into. The UserFactorType enum already advertises signed_nonce as 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

  1. Missing discriminator entryUserFactor.discriminator.mapping in openapi/api.yaml had no signed_nonce → schema mapping.
  2. Missing subschema — No UserFactorSignedNonce (or supporting UserFactorSignedNonceProfile / 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.yaml

  1. Added the discriminator mapping:

    UserFactor:
      discriminator:
        propertyName: factorType
        mapping:
          # ...existing mappings...
          signed_nonce: '#/components/schemas/UserFactorSignedNonce'
  2. Added three new component schemas:

    • UserFactorSignedNonceallOf extension of UserFactor, pinning factorType: signed_nonce, provider: OKTA, and a profile reference. 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 a keys[] array.
    • UserFactorSignedNonceProfileKey — JWK-style key entry: kty (RSA/EC), use, kid, jwkType (proofOfPossession/userVerification/userVerificationBioOrPin), RSA fields (e, n), EC fields (crv, x, y), and x5c.

Generated model changes

  1. okta/models/user_factor_signed_nonce.py (new, generated)
  2. okta/models/user_factor_signed_nonce_profile.py (new, generated)
  3. okta/models/user_factor_signed_nonce_profile_key.py (new, generated)

Discriminator routing — okta/models/user_factor.py

  1. Registered UserFactorSignedNonce in:
    • The __discriminator_value_class_map ("signed_nonce": "UserFactorSignedNonce").
    • The Union[...] of concrete subclasses returned by from_json / from_dict.
    • The explicit from_dict() dispatch chain (with the same self-recursion guard used for the other variants).
    • The TYPE_CHECKING import block.

Lazy-import wiring

  1. okta/__init__.py — Added the three new classes to _LAZY_IMPORT_MAP so they can be imported as from okta import UserFactorSignedNonce.
  2. okta/models/__init__.py — Added the same entries to the model-level _LAZY_IMPORT_MAP and added UserFactorSignedNonce to the user_factor model group in _MODEL_GROUPS.

Docs

  1. docs/UserFactorSignedNonce.md (new)
  2. docs/UserFactorSignedNonceProfile.md (new)
  3. docs/UserFactorSignedNonceProfileKey.md (new)

Files Changed

File Type Purpose
openapi/api.yaml modified Add discriminator mapping + 3 new schemas
okta/models/user_factor.py modified Register new subclass in discriminator/Union/dispatch
okta/models/user_factor_signed_nonce.py new (generated) Pydantic model for the factor
okta/models/user_factor_signed_nonce_profile.py new (generated) Pydantic model for device profile
okta/models/user_factor_signed_nonce_profile_key.py new (generated) Pydantic model for JWK key entry
okta/__init__.py modified Lazy-import map entries
okta/models/__init__.py modified Lazy-import map + user_factor group entry
docs/UserFactorSignedNonce.md new API docs
docs/UserFactorSignedNonceProfile.md new API docs
docs/UserFactorSignedNonceProfileKey.md new API docs

Testing

  • ✅ Verified against a live Okta org with a user enrolled in Okta FastPass (signed_nonce): client.list_factors(user_id) returns the complete factor list with no validation errors.
  • UserFactorSignedNonce instances deserialize correctly, with populated profile.deviceType, profile.platform, profile.version, and profile.keys[] (including both RSA and EC keys, proofOfPossession and userVerification variants).
  • ✅ No regressions on other factor types (push, webauthn, token:software:totp, etc.) — the discriminator map still routes all existing values to their original subclasses.
  • ✅ Lazy import works: from okta import UserFactorSignedNonce resolves through _LAZY_IMPORT_MAP without eagerly loading all 1000+ models.

Backward Compatibility

  • Additive only. No existing schema, model, mapping, or method signature is changed.
  • Consumers that previously caught the ValidationError from list_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 canonical api.yaml source, regenerating the SDK from the upstream spec should produce equivalent output to this PR.

…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
@BinoyOza-okta BinoyOza-okta self-assigned this Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant