Repository: duo-labs/py_webauthn

Issue:
Forced dependency on pydantic for library users
Users of this library need to explicitly rely on symbols that are implemented in pydantic because the public interface expects pydantic instances that have to be built using `{RegistrationCredential, AuthenticationCredential}.parse_raw` (a _v1-ism_) or `.model_validate_json`  (equivalent in v2).

In this way, `py_webauthn` dependency on `pydantic` is "contagious". It's even more complicated because the coupling is to a specific version of `pydantic`. Additionally to the `.parse_raw`/`.model_validate_json` there's also the extra need to depend on `pydantic.ValidationError`.


Discussion:
CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/duo-labs/py_webauthn?pullRequest=172) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/duo-labs/py_webauthn?pullRequest=172) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/duo-labs/py_webauthn?pullRequest=172) it.</sub>

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 000a05c..031163c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,10 @@
 # Changelog
+## Unreleased
+
+**Changes:**
+
+- Avoid reliance on pydantic symbols in the public interface ([#172](https://github.com/duo-labs/py_webauthn/pull/172))
+- Allow `verify_registration_response` and `verify_authentication_response` to receive credentials in JSON format
 
 ## v1.10.1
 
diff --git a/README.md b/README.md
index d00e69f..bfdd883 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ Generally, the library makes the following assumptions about how a Relying Party
 
 The examples mentioned below include uses of the `options_to_json()` helper (see above) to show how easily `bytes` values in registration and authentication options can be encoded to base64url for transmission to the front end.
 
-The examples also include demonstrations of how to pass JSON-ified responses, using base64url encoding for `ArrayBuffer` values, into `RegistrationCredential.parse_raw()` and `AuthenticationCredential.parse_raw()` to be automatically parsed by the methods in this library. An RP can pair this with corresponding custom front end logic, or one of several frontend-specific libraries (like [@simplewebauthn/browser](https://www.npmjs.com/package/@simplewebauthn/browser), for example) to handle encoding and decoding such values to and from JSON.
+The examples also include demonstrations of how to pass JSON-ified responses, using base64url encoding for `ArrayBuffer` values, into `parse_registration_credential_json` and `parse_authentication_credential_json` to be automatically parsed by the methods in this library. An RP can pair this with corresponding custom front end logic, or one of several frontend-specific libraries (like [@simplewebauthn/browser](https://www.npmjs.com/package/@simplewebauthn/browser), for example) to handle encoding and decoding such values to and from JSON.
 
 Other arguments into this library's methods that are defined as `bytes` are intended to be values stored entirely on the server. Such values can more easily exist as `bytes` without needing potentially extraneous encoding and decoding into other formats. Any encoding or decoding of such values in the name of storing them between steps in a WebAuthn ceremony is left up to the RP to achieve in an implementation-specific manner.
 
diff --git a/examples/authentication.py b/examples/authentication.py
index 5f6bfa4..fe3a054 100644
--- a/examples/authentication.py
+++ b/examples/authentication.py
@@ -5,9 +5,9 @@ from webauthn import (
     base64url_to_bytes,
 )
 from webauthn.helpers.structs import (
+    PYDANTIC_V2,
     PublicKeyCredentialDescriptor,
     UserVerificationRequirement,
-    AuthenticationCredential,
 )
 
 ################
@@ -40,8 +40,7 @@ print(options_to_json(complex_authentication_options))
 
 # Authentication Response Verification
 authentication_verification = verify_authentication_response(
-    credential=AuthenticationCredential.parse_raw(
-        """{
+    credential="""{
         "id": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s",
         "rawId": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s",
         "response": {
@@ -53,8 +52,7 @@ authentication_verification = verify_authentication_response(
         "type": "public-key",
         "authenticatorAttachment": "cross-platform",
         "clientExtensionResults": {}
-    }"""
-    ),
+    }""",
     expected_challenge=base64url_to_bytes(
         "iPmAi1Pp1XL6oAgq3PWZtZPnZa1zFUDoGbaQ0_KvVG1lF2s3Rt_3o4uSzccy0tmcTIpTTT4BU1T-I4maavndjQ"
     ),
@@ -67,5 +65,8 @@ authentication_verification = verify_authentication_response(
     require_user_verification=True,
 )
 print("\n[Authentication Verification]")
-print(authentication_verification.json(indent=2))
+if PYDANTIC_V2:
+    print(authentication_verification.model_dump_json(indent=2))
+else:
+    print(authentication_verification.json(indent=2))
 assert authentication_verification.new_sign_count == 1
diff --git a/examples/registration.py b/examples/registration.py
index 8f94ea4..6ac162c 100644
--- a/examples/registration.py
+++ b/examples/registration.py
@@ -6,12 +6,12 @@ from webauthn import (
 )
 from webauthn.helpers.cose import COSEAlgorithmIdentifier
 from webauthn.helpers.structs import (
+    PYDANTIC_V2,
     AttestationConveyancePreference,
     AuthenticatorAttachment,
     AuthenticatorSelectionCriteria,
     PublicKeyCredentialDescriptor,
     ResidentKeyRequirement,
-    RegistrationCredential,
 )
 
 ################
@@ -56,8 +56,7 @@ print(options_to_json(complex_registration_options))
 
 # Registration Response Verification
 registration_verification = verify_registration_response(
-    credential=RegistrationCredential.parse_raw(
-        """{
+    credential="""{
         "id": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s",
         "rawId": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s",
         "response": {
@@ -68,8 +67,7 @@ registration_verification = verify_registration_response(
         "type": "public-key",
         "clientExtensionResults": {},
         "authenticatorAttachment": "platform"
-    }"""
-    ),
+    }""",
     expected_challenge=base64url_to_bytes(
         "CeTWogmg0cchuiYuFrv8DXXdMZSIQRVZJOga_xayVVEcBj0Cw3y73yhD4FkGSe-RrP6hPJJAIm3LVien4hXELg"
     ),
@@ -79,7 +77,10 @@ registration_verification = verify_registration_response(
 )
 
 print("\n[Registration Verification - None]")
-print(registration_verification.json(indent=2))
+if PYDANTIC_V2:
+    print(registration_verification.model_dump_json(indent=2))
+else:
+    print(registration_verification.json(indent=2))
 assert registration_verification.credential_id == base64url_to_bytes(
     "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s"
 )
diff --git a/webauthn/authentication/verify_authentication_response.py b/webauthn/authentication/verify_authentication_response.py
index c16f0ff..55fe0ae 100644
--- a/webauthn/authentication/verify_authentication_response.py
+++ b/webauthn/authentication/verify_authentication_response.py
@@ -10,6 +10,7 @@ from webauthn.helpers import (
     parse_authenticator_data,
     parse_backup_flags,
     parse_client_data_json,
+    parse_authentication_credential_json,
     verify_signature,
 )
 from webauthn.helpers.exceptions import InvalidAuthenticationResponse
@@ -42,7 +43,7 @@ expected_token_binding_statuses = [
 
 def verify_authentication_response(
     *,
-    credential: AuthenticationCredential,
+    credential: Union[str, AuthenticationCredential],
     expected_challenge: bytes,
     expected_rp_id: str,
     expected_origin: Union[str, List[str]],
@@ -67,6 +68,8 @@ def verify_authentication_response(
     Raises:
         `helpers.exceptions.InvalidAuthenticationResponse` if the response cannot be verified
     """
+    if isinstance(credential, str):
+        credential = parse_authentication_credential_json(credential)
 
     # FIDO-specific check
     if bytes_to_base64url(credential.raw_id) != credential.id:
@@ -112,7 +115,7 @@ def verify_authentication_response(
                 f'Unexpected token_binding status of "{status}", expected one of "{",".join(expected_token_binding_statuses)}"'
             )
 
-    auth_data = parse_authenticator_data(response.authenticator_data)
+    auth_data = parse_authenticator_data(response.authenticator_data)  # TODO: Issue #173
 
     # Generate a hash of the expected RP ID for comparison
     expected_rp_id_hash = hashlib.sha256()
diff --git a/webauthn/helpers/__init__.py b/webauthn/helpers/__init__.py
index d91c1f3..ff3fdd9 100644
--- a/webauthn/helpers/__init__.py
+++ b/webauthn/helpers/__init__.py
@@ -9,9 +9,11 @@ from .hash_by_alg import hash_by_alg
 from .json_loads_base64url_to_bytes import json_loads_base64url_to_bytes
 from .options_to_json import options_to_json
 from .parse_attestation_object import parse_attestation_object
+from .parse_authentication_credential_json import parse_authentication_credential_json
 from .parse_authenticator_data import parse_authenticator_data
 from .parse_backup_flags import parse_backup_flags
 from .parse_client_data_json import parse_client_data_json
+from .parse_registration_credential_json import parse_registration_credential_json
 from .validate_certificate_chain import validate_certificate_chain
 from .verify_safetynet_timestamp import verify_safetynet_timestamp
 from .verify_signature import verify_signature
@@ -29,8 +31,10 @@ __all__ = [
     "options_to_json",
     "parse_attestation_object",
     "parse_authenticator_data",
+    "parse_authentication_credential_json",
     "parse_backup_flags",
     "parse_client_data_json",
+    "parse_registration_credential_json",
     "validate_certificate_chain",
     "verify_safetynet_timestamp",
     "verify_signature",
diff --git a/webauthn/helpers/options_to_json.py b/webauthn/helpers/options_to_json.py
index 538cd99..658b073 100644
--- a/webauthn/helpers/options_to_json.py
+++ b/webauthn/helpers/options_to_json.py
@@ -1,6 +1,7 @@
 from typing import Union
 
 from .structs import (
+    PYDANTIC_V2,
     PublicKeyCredentialCreationOptions,
     PublicKeyCredentialRequestOptions,
 )
@@ -15,8 +16,18 @@ def options_to_json(
     """
     Prepare options for transmission to the front end as JSON
     """
-    return options.json(
-        by_alias=True,
-        exclude_unset=False,
-        exclude_none=True,
-    )
+    if PYDANTIC_V2:
+        json_options = options.model_dump_json(  # type: ignore[union-attr]
+            by_alias=True,
+            exclude_unset=False,
+            exclude_none=True,
+        )
+
+    else:
+        json_options = options.json(
+            by_alias=True,
+            exclude_unset=False,
+            exclude_none=True,
+        )
+
+    return json_options
diff --git a/webauthn/helpers/parse_authentication_credential_json.py b/webauthn/helpers/parse_authentication_credential_json.py
new file mode 100644
index 0000000..5d5d8b8
--- /dev/null
+++ b/webauthn/helpers/parse_authentication_credential_json.py
@@ -0,0 +1,21 @@
+from typing import Callable
+from pydantic import ValidationError
+
+from .exceptions import InvalidAuthenticationResponse
+from .structs import PYDANTIC_V2, AuthenticationCredential
+
+
+def parse_authentication_credential_json(json_val: str) -> AuthenticationCredential:
+    if PYDANTIC_V2:
+        parsing_method: Callable = AuthenticationCredential.model_validate_json  # type: ignore[attr-defined]
+    else:  # assuming V1
+        parsing_method = AuthenticationCredential.parse_raw
+
+    try:
+        authentication_credential = parsing_method(json_val)
+    except ValidationError as exc:
+        raise InvalidAuthenticationResponse(
+            "Unable to parse an authentication credential from JSON data"
+        ) from exc
+
+    return authentication_credential
diff --git a/webauthn/helpers/parse_registration_credential_json.py b/webauthn/helpers/parse_registration_credential_json.py
new file mode 100644
index 0000000..11a3c51
--- /dev/null
+++ b/webauthn/helpers/parse_registration_credential_json.py
@@ -0,0 +1,21 @@
+from typing import Callable
+from pydantic import ValidationError
+
+from .exceptions import InvalidRegistrationResponse
+from .structs import PYDANTIC_V2, RegistrationCredential
+
+
+def parse_registration_credential_json(json_val: str) -> RegistrationCredential:
+    if PYDANTIC_V2:
+        parsing_method: Callable = RegistrationCredential.model_validate_json  # type: ignore[attr-defined]
+    else:  # assuming V1
+        parsing_method = RegistrationCredential.parse_raw
+
+    try:
+        registration_credential = parsing_method(json_val)
+    except ValidationError as exc:
+        raise InvalidRegistrationResponse(
+            "Unable to parse a registration credential from JSON data"
+        ) from exc
+
+    return registration_credential
diff --git a/webauthn/registration/formats/android_safetynet.py b/webauthn/registration/formats/android_safetynet.py
index 7ca96cf..5acf807 100644
--- a/webauthn/registration/formats/android_safetynet.py
+++ b/webauthn/registration/formats/android_safetynet.py
@@ -20,7 +20,7 @@ from webauthn.helpers.exceptions import (
     InvalidRegistrationResponse,
 )
 from webauthn.helpers.known_root_certs import globalsign_r2, globalsign_root_ca
-from webauthn.helpers.structs import AttestationStatement, WebAuthnBaseModel
+from webauthn.helpers.structs import PYDANTIC_V2, AttestationStatement, WebAuthnBaseModel
 
 
 class SafetyNetJWSHeader(WebAuthnBaseModel):
@@ -87,8 +87,13 @@ def verify_android_safetynet(
             "Response JWS did not have three parts (SafetyNet)"
         )
 
-    header = SafetyNetJWSHeader.parse_raw(base64url_to_bytes(jws_parts[0]))
-    payload = SafetyNetJWSPayload.parse_raw(base64url_to_bytes(jws_parts[1]))
+    if PYDANTIC_V2:
+        header = SafetyNetJWSHeader.model_validate_json(base64url_to_bytes(jws_parts[0]))  # type: ignore[attr-defined]
+        payload = SafetyNetJWSPayload.model_validate_json(base64url_to_bytes(jws_parts[1]))  # type: ignore[attr-defined]
+    else:
+        header = SafetyNetJWSHeader.parse_raw(base64url_to_bytes(jws_parts[0]))
+        payload = SafetyNetJWSPayload.parse_raw(base64url_to_bytes(jws_parts[1]))
+
     signature_bytes_str: str = jws_parts[2]
 
     # Verify that the nonce attribute in the payload of response is identical to the
diff --git a/webauthn/registration/verify_registration_response.py b/webauthn/registration/verify_registration_response.py
index 46bd5f6..562b884 100644
--- a/webauthn/registration/verify_registration_response.py
+++ b/webauthn/registration/verify_registration_response.py
@@ -8,10 +8,12 @@ from webauthn.helpers import (
     parse_attestation_object,
     parse_client_data_json,
     parse_backup_flags,
+    parse_registration_credential_json,
 )
 from webauthn.helpers.cose import COSEAlgorithmIdentifier
 from webauthn.helpers.exceptions import InvalidRegistrationResponse
 from webauthn.helpers.structs import (
+    PYDANTIC_V2,
     AttestationFormat,
     ClientDataType,
     CredentialDeviceType,
@@ -63,7 +65,7 @@ expected_token_binding_statuses = [
 
 def verify_registration_response(
     *,
-    credential: RegistrationCredential,
+    credential: Union[str, RegistrationCredential],
     expected_challenge: bytes,
     expected_rp_id: str,
     expected_origin: Union[str, List[str]],
@@ -91,6 +93,8 @@ def verify_registration_response(
     Raises:
         `helpers.exceptions.InvalidRegistrationResponse` if the response cannot be verified
     """
+    if isinstance(credential, str):
+        credential = parse_registration_credential_json(credential)
 
     verified = False
 
@@ -138,7 +142,7 @@ def verify_registration_response(
                 f'Unexpected token_binding status of "{status}", expected one of "{",".join(expected_token_binding_statuses)}"'
             )
 
-    attestation_object = parse_attestation_object(response.attestation_object)
+    attestation_object = parse_attestation_object(response.attestation_object)  # TODO: Issue #173
 
     auth_data = attestation_object.auth_data
 
@@ -198,7 +202,11 @@ def verify_registration_response(
     if attestation_object.fmt == AttestationFormat.NONE:
         # A "none" attestation should not contain _anything_ in its attestation
         # statement
-        num_att_stmt_fields_set = len(attestation_object.att_stmt.__fields_set__)
+        if PYDANTIC_V2:
+            num_att_stmt_fields_set = len(attestation_object.att_stmt.model_fields_set)  # type: ignore[attr-defined]
+        else:
+            num_att_stmt_fields_set = len(attestation_object.att_stmt.__fields_set__)
+
         if num_att_stmt_fields_set > 0:
             raise InvalidRegistrationResponse(
                 "None attestation had unexpected attestation statement"


Test patch:
diff --git a/tests/test_structs.py b/tests/test_structs.py
index 5b8787d..b4618e1 100644
--- a/tests/test_structs.py
+++ b/tests/test_structs.py
@@ -1,19 +1,19 @@
 from unittest import TestCase
 
+from webauthn.helpers import parse_registration_credential_json
 from webauthn.helpers.structs import (
-    RegistrationCredential,
     AuthenticatorTransport,
     AuthenticatorAttachment,
 )
 
 
 class TestStructsRegistrationCredential(TestCase):
-    def test_registration_credential_parse_raw(self):
+    def test_parse_registration_credential_json(self):
         """
         Check that we can properly parse some values that aren't really here-or-there for response
         verification, but can still be useful to RP's to fine-tune the WebAuthn experience.
         """
-        parsed = RegistrationCredential.parse_raw(
+        parsed = parse_registration_credential_json(
             """{
                 "id": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s",
                 "rawId": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s",
diff --git a/tests/test_verify_authentication_response.py b/tests/test_verify_authentication_response.py
index accaef0..f7fcb42 100644
--- a/tests/test_verify_authentication_response.py
+++ b/tests/test_verify_authentication_response.py
@@ -1,15 +1,13 @@
 from unittest import TestCase
 
 from webauthn import verify_authentication_response
-from webauthn.helpers import base64url_to_bytes
+from webauthn.helpers import base64url_to_bytes, parse_authentication_credential_json
 from webauthn.helpers.exceptions import InvalidAuthenticationResponse
-from webauthn.helpers.structs import AuthenticationCredential
 
 
 class TestVerifyAuthenticationResponse(TestCase):
     def test_verify_authentication_response_with_EC2_public_key(self):
-        credential = AuthenticationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "EDx9FfAbp4obx6oll2oC4-CZuDidRVV4gZhxC529ytlnqHyqCStDUwfNdm1SNHAe3X5KvueWQdAX3x9R1a2b9Q",
             "rawId": "EDx9FfAbp4obx6oll2oC4-CZuDidRVV4gZhxC529ytlnqHyqCStDUwfNdm1SNHAe3X5KvueWQdAX3x9R1a2b9Q",
             "response": {
@@ -20,7 +18,6 @@ class TestVerifyAuthenticationResponse(TestCase):
             "type": "public-key",
             "clientExtensionResults": {}
         }"""
-        )
         challenge = base64url_to_bytes(
             "xi30GPGAFYRxVDpY1sM10DaLzVQG66nv-_7RUazH0vI2YvG8LYgDEnvN5fZZNVuvEDuMi9te3VLqb42N0fkLGA"
         )
@@ -48,20 +45,18 @@ class TestVerifyAuthenticationResponse(TestCase):
         assert verification.credential_device_type == 'single_device'
 
     def test_verify_authentication_response_with_RSA_public_key(self):
-        credential = AuthenticationCredential.parse_raw(
-            """{
-                "id": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s",
-                "rawId": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s",
-                "response": {
-                    "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAQ",
-                    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiaVBtQWkxUHAxWEw2b0FncTNQV1p0WlBuWmExekZVRG9HYmFRMF9LdlZHMWxGMnMzUnRfM280dVN6Y2N5MHRtY1RJcFRUVDRCVTFULUk0bWFhdm5kalEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9",
-                    "signature": "iOHKX3erU5_OYP_r_9HLZ-CexCE4bQRrxM8WmuoKTDdhAnZSeTP0sjECjvjfeS8MJzN1ArmvV0H0C3yy_FdRFfcpUPZzdZ7bBcmPh1XPdxRwY747OrIzcTLTFQUPdn1U-izCZtP_78VGw9pCpdMsv4CUzZdJbEcRtQuRS03qUjqDaovoJhOqEBmxJn9Wu8tBi_Qx7A33RbYjlfyLm_EDqimzDZhyietyop6XUcpKarKqVH0M6mMrM5zTjp8xf3W7odFCadXEJg-ERZqFM0-9Uup6kJNLbr6C5J4NDYmSm3HCSA6lp2iEiMPKU8Ii7QZ61kybXLxsX4w4Dm3fOLjmDw",
-                    "userHandle": "T1RWa1l6VXdPRFV0WW1NNVlTMDBOVEkxTFRnd056Z3RabVZpWVdZNFpEVm1ZMk5p"
-                },
-                "type": "public-key",
-                "clientExtensionResults": {}
-            }"""
-        )
+        credential = """{
+            "id": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s",
+            "rawId": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s",
+            "response": {
+                "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAQ",
+                "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiaVBtQWkxUHAxWEw2b0FncTNQV1p0WlBuWmExekZVRG9HYmFRMF9LdlZHMWxGMnMzUnRfM280dVN6Y2N5MHRtY1RJcFRUVDRCVTFULUk0bWFhdm5kalEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9",
+                "signature": "iOHKX3erU5_OYP_r_9HLZ-CexCE4bQRrxM8WmuoKTDdhAnZSeTP0sjECjvjfeS8MJzN1ArmvV0H0C3yy_FdRFfcpUPZzdZ7bBcmPh1XPdxRwY747OrIzcTLTFQUPdn1U-izCZtP_78VGw9pCpdMsv4CUzZdJbEcRtQuRS03qUjqDaovoJhOqEBmxJn9Wu8tBi_Qx7A33RbYjlfyLm_EDqimzDZhyietyop6XUcpKarKqVH0M6mMrM5zTjp8xf3W7odFCadXEJg-ERZqFM0-9Uup6kJNLbr6C5J4NDYmSm3HCSA6lp2iEiMPKU8Ii7QZ61kybXLxsX4w4Dm3fOLjmDw",
+                "userHandle": "T1RWa1l6VXdPRFV0WW1NNVlTMDBOVEkxTFRnd056Z3RabVZpWVdZNFpEVm1ZMk5p"
+            },
+            "type": "public-key",
+            "clientExtensionResults": {}
+        }"""
         challenge = base64url_to_bytes(
             "iPmAi1Pp1XL6oAgq3PWZtZPnZa1zFUDoGbaQ0_KvVG1lF2s3Rt_3o4uSzccy0tmcTIpTTT4BU1T-I4maavndjQ"
         )
@@ -85,8 +80,7 @@ class TestVerifyAuthenticationResponse(TestCase):
         assert verification.new_sign_count == 1
 
     def test_raises_exception_on_incorrect_public_key(self):
-        credential = AuthenticationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "FviUBZA3FGMxEm3A1K2T8MhuEBLp4qQsV9ScAKYrpdw2kbGnqx24tF4ev6PEHEYC3g8z6HMJh7dYHe3Uuq7_8Q",
             "rawId": "FviUBZA3FGMxEm3A1K2T8MhuEBLp4qQsV9ScAKYrpdw2kbGnqx24tF4ev6PEHEYC3g8z6HMJh7dYHe3Uuq7_8Q",
             "response": {
@@ -97,7 +91,6 @@ class TestVerifyAuthenticationResponse(TestCase):
             "type": "public-key",
             "clientExtensionResults": {}
         }"""
-        )
         challenge = base64url_to_bytes(
             "zsfiMZj16TUVCrT5tDRYXdYlUrJp7zn_UNd5NmBocPc4I2dKZbeEWpwBAwA4s6oHkVX6_ly_jgp743dyiWHYYw"
         )
@@ -123,8 +116,7 @@ class TestVerifyAuthenticationResponse(TestCase):
             )
 
     def test_raises_exception_on_uv_required_but_false(self):
-        credential = AuthenticationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "4-5MZF69j3n2B6Z99dUN0fNrAQmrjELJIebWVw8aKfw1EQKg28Tx40R_kw-1pcrfSgJFKm3mCtAtBgSRWgDMng",
             "rawId": "4-5MZF69j3n2B6Z99dUN0fNrAQmrjELJIebWVw8aKfw1EQKg28Tx40R_kw-1pcrfSgJFKm3mCtAtBgSRWgDMng",
             "response": {
@@ -135,7 +127,6 @@ class TestVerifyAuthenticationResponse(TestCase):
             "type": "public-key",
             "clientExtensionResults": {}
         }"""
-        )
         challenge = base64url_to_bytes(
             "umGemXJIPBXPxkD8Hjanuv9BDor8Z7O3aPdtOgMCdW4PAfqDX43EFlhrsF0PW90df5zrgbt7YVMRAa27tCdHzw"
         )
@@ -161,19 +152,17 @@ class TestVerifyAuthenticationResponse(TestCase):
             )
 
     def test_verify_authentication_response_with_OKP_public_key(self):
-        credential = AuthenticationCredential.parse_raw(
-            """{
-                "id": "fq9Nj0nS24B5y6Pkw_h3-9GEAEA3-0LBPxE2zvTdLjDqtSeCSNYFe9VMRueSpAZxT3YDc6L1lWXdQNwI-sVNYrefEcRR1Nsb_0jpHE955WEtFud2xxZg3MvoLMxHLet63i5tajd1fHtP7I-00D6cehM8ZWlLp2T3s9lfZgVIFcA",
-                "rawId": "fq9Nj0nS24B5y6Pkw_h3-9GEAEA3-0LBPxE2zvTdLjDqtSeCSNYFe9VMRueSpAZxT3YDc6L1lWXdQNwI-sVNYrefEcRR1Nsb_0jpHE955WEtFud2xxZg3MvoLMxHLet63i5tajd1fHtP7I-00D6cehM8ZWlLp2T3s9lfZgVIFcA",
-                "response": {
-                    "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAABw",
-                    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZVo0ZWVBM080ank1Rkl6cURhU0o2SkROR3UwYkJjNXpJMURqUV9rTHNvMVdOcWtHNms1bUNZZjFkdFFoVlVpQldaV2xaa3pSNU1GZWVXQ3BKUlVOWHciLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9",
-                    "signature": "RRWV8mYDRvK7YdQgdtZD4pJ2dh1D_IWZ_D6jsZo6FHJBoenbj0CVT5nA20vUzlRhN4R6dOEUHmUwP1F8eRBhBg"
-                },
-                "type": "public-key",
-                "clientExtensionResults": {}
-            }"""
-        )
+        credential = """{
+            "id": "fq9Nj0nS24B5y6Pkw_h3-9GEAEA3-0LBPxE2zvTdLjDqtSeCSNYFe9VMRueSpAZxT3YDc6L1lWXdQNwI-sVNYrefEcRR1Nsb_0jpHE955WEtFud2xxZg3MvoLMxHLet63i5tajd1fHtP7I-00D6cehM8ZWlLp2T3s9lfZgVIFcA",
+            "rawId": "fq9Nj0nS24B5y6Pkw_h3-9GEAEA3-0LBPxE2zvTdLjDqtSeCSNYFe9VMRueSpAZxT3YDc6L1lWXdQNwI-sVNYrefEcRR1Nsb_0jpHE955WEtFud2xxZg3MvoLMxHLet63i5tajd1fHtP7I-00D6cehM8ZWlLp2T3s9lfZgVIFcA",
+            "response": {
+                "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAABw",
+                "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZVo0ZWVBM080ank1Rkl6cURhU0o2SkROR3UwYkJjNXpJMURqUV9rTHNvMVdOcWtHNms1bUNZZjFkdFFoVlVpQldaV2xaa3pSNU1GZWVXQ3BKUlVOWHciLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9",
+                "signature": "RRWV8mYDRvK7YdQgdtZD4pJ2dh1D_IWZ_D6jsZo6FHJBoenbj0CVT5nA20vUzlRhN4R6dOEUHmUwP1F8eRBhBg"
+            },
+            "type": "public-key",
+            "clientExtensionResults": {}
+        }"""
         challenge = base64url_to_bytes(
             "eZ4eeA3O4jy5FIzqDaSJ6JDNGu0bBc5zI1DjQ_kLso1WNqkG6k5mCYf1dtQhVUiBWZWlZkzR5MFeeWCpJRUNXw"
         )
@@ -196,8 +185,7 @@ class TestVerifyAuthenticationResponse(TestCase):
         assert verification.new_sign_count == 7
 
     def test_supports_multiple_expected_origins(self) -> None:
-        credential = AuthenticationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "AXmOjWWZH67pgl5_gAbKVBqoL2dyHHGEWZLspIsCwULG0hZ3HyuGgvkaRcSOLq9W72XtegcvFYXIdlafrilbtVnx2Q14gNbfSQQP2sgNEAif4MjHtGpeVB0BfFawCs85Y3XY_j4sxthVnyTY_Q",
             "rawId": "AXmOjWWZH67pgl5_gAbKVBqoL2dyHHGEWZLspIsCwULG0hZ3HyuGgvkaRcSOLq9W72XtegcvFYXIdlafrilbtVnx2Q14gNbfSQQP2sgNEAif4MjHtGpeVB0BfFawCs85Y3XY_j4sxthVnyTY_Q",
             "response": {
@@ -209,7 +197,6 @@ class TestVerifyAuthenticationResponse(TestCase):
             "type": "public-key",
             "clientExtensionResults": {}
         }"""
-        )
 
         challenge = base64url_to_bytes(
             "6zrSRX8CxwPSXEArXyXL2tpb6rB7Star0rLVIj5rvf74mZKF5ir715Xmgz5tA_GyHexN4oXfrQ889ArVCLaRdA"
@@ -233,3 +220,38 @@ class TestVerifyAuthenticationResponse(TestCase):
         assert verification.credential_id == base64url_to_bytes(
             "AXmOjWWZH67pgl5_gAbKVBqoL2dyHHGEWZLspIsCwULG0hZ3HyuGgvkaRcSOLq9W72XtegcvFYXIdlafrilbtVnx2Q14gNbfSQQP2sgNEAif4MjHtGpeVB0BfFawCs85Y3XY_j4sxthVnyTY_Q"
         )
+
+    def test_supports_already_parsed_credential(self) -> None:
+        parsed_credential = parse_authentication_credential_json("""{
+            "id": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s",
+            "rawId": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s",
+            "response": {
+                "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAQ",
+                "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiaVBtQWkxUHAxWEw2b0FncTNQV1p0WlBuWmExekZVRG9HYmFRMF9LdlZHMWxGMnMzUnRfM280dVN6Y2N5MHRtY1RJcFRUVDRCVTFULUk0bWFhdm5kalEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9",
+                "signature": "iOHKX3erU5_OYP_r_9HLZ-CexCE4bQRrxM8WmuoKTDdhAnZSeTP0sjECjvjfeS8MJzN1ArmvV0H0C3yy_FdRFfcpUPZzdZ7bBcmPh1XPdxRwY747OrIzcTLTFQUPdn1U-izCZtP_78VGw9pCpdMsv4CUzZdJbEcRtQuRS03qUjqDaovoJhOqEBmxJn9Wu8tBi_Qx7A33RbYjlfyLm_EDqimzDZhyietyop6XUcpKarKqVH0M6mMrM5zTjp8xf3W7odFCadXEJg-ERZqFM0-9Uup6kJNLbr6C5J4NDYmSm3HCSA6lp2iEiMPKU8Ii7QZ61kybXLxsX4w4Dm3fOLjmDw",
+                "userHandle": "T1RWa1l6VXdPRFV0WW1NNVlTMDBOVEkxTFRnd056Z3RabVZpWVdZNFpEVm1ZMk5p"
+            },
+            "type": "public-key",
+            "clientExtensionResults": {}
+        }""")
+        challenge = base64url_to_bytes(
+            "iPmAi1Pp1XL6oAgq3PWZtZPnZa1zFUDoGbaQ0_KvVG1lF2s3Rt_3o4uSzccy0tmcTIpTTT4BU1T-I4maavndjQ"
+        )
+        expected_rp_id = "localhost"
+        expected_origin = "http://localhost:5000"
+        credential_public_key = base64url_to_bytes(
+            "pAEDAzkBACBZAQDfV20epzvQP-HtcdDpX-cGzdOxy73WQEvsU7Dnr9UWJophEfpngouvgnRLXaEUn_d8HGkp_HIx8rrpkx4BVs6X_B6ZjhLlezjIdJbLbVeb92BaEsmNn1HW2N9Xj2QM8cH-yx28_vCjf82ahQ9gyAr552Bn96G22n8jqFRQKdVpO-f-bvpvaP3IQ9F5LCX7CUaxptgbog1SFO6FI6ob5SlVVB00lVXsaYg8cIDZxCkkENkGiFPgwEaZ7995SCbiyCpUJbMqToLMgojPkAhWeyktu7TlK6UBWdJMHc3FPAIs0lH_2_2hKS-mGI1uZAFVAfW1X-mzKL0czUm2P1UlUox7IUMBAAE"
+        )
+        sign_count = 0
+
+        verification = verify_authentication_response(
+            credential=parsed_credential,
+            expected_challenge=challenge,
+            expected_rp_id=expected_rp_id,
+            expected_origin=expected_origin,
+            credential_public_key=credential_public_key,
+            credential_current_sign_count=sign_count,
+            require_user_verification=True,
+        )
+
+        assert verification.new_sign_count == 1
diff --git a/tests/test_verify_registration_response.py b/tests/test_verify_registration_response.py
index 7dab1df..6279e26 100644
--- a/tests/test_verify_registration_response.py
+++ b/tests/test_verify_registration_response.py
@@ -3,21 +3,19 @@ from unittest import TestCase
 
 import cbor2
 from pydantic import ValidationError
-from webauthn.helpers import base64url_to_bytes, bytes_to_base64url
+from webauthn.helpers import base64url_to_bytes, bytes_to_base64url, parse_registration_credential_json
 from webauthn.helpers.exceptions import InvalidRegistrationResponse
 from webauthn.helpers.known_root_certs import globalsign_r2
 from webauthn.helpers.structs import (
     AttestationFormat,
     PublicKeyCredentialType,
-    RegistrationCredential,
 )
 from webauthn import verify_registration_response
 
 
 class TestVerifyRegistrationResponse(TestCase):
     def test_verifies_none_attestation_response(self) -> None:
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "9y1xA8Tmg1FEmT-c7_fvWZ_uoTuoih3OvR45_oAK-cwHWhAbXrl2q62iLVTjiyEZ7O7n-CROOY494k7Q3xrs_w",
             "rawId": "9y1xA8Tmg1FEmT-c7_fvWZ_uoTuoih3OvR45_oAK-cwHWhAbXrl2q62iLVTjiyEZ7O7n-CROOY494k7Q3xrs_w",
             "response": {
@@ -31,7 +29,6 @@ class TestVerifyRegistrationResponse(TestCase):
                 "usb"
             ]
         }"""
-        )
 
         challenge = base64url_to_bytes(
             "TwN7n4WTyGKLc4ZY-qGsFqKnHM4nglqsyV0ICJlN2TO9XiRyFtrkaDwUvsql-gkLJXP6fnF1MlrZ53Mm4R7Cvw"
@@ -81,7 +78,7 @@ class TestVerifyRegistrationResponse(TestCase):
         parsed_atte_obj["fmt"] = "not_real_fmt"
         cred_json["response"]["attestationObject"] = bytes_to_base64url(cbor2.dumps(parsed_atte_obj))  # type: ignore
 
-        credential = RegistrationCredential.parse_raw(json.dumps(cred_json))
+        credential = json.dumps(cred_json)
         challenge = base64url_to_bytes(
             "pDRmkdduAi-AU2x6o-FqqhI3XK2nlVlsCSr04zWkNtv84JwrMHtElRHHUWLEDhkrEaQ8B1lBcIH_VSRqp_RAAw"
         )
@@ -97,8 +94,7 @@ class TestVerifyRegistrationResponse(TestCase):
             )
 
     def test_supports_multiple_expected_origins(self) -> None:
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "9y1xA8Tmg1FEmT-c7_fvWZ_uoTuoih3OvR45_oAK-cwHWhAbXrl2q62iLVTjiyEZ7O7n-CROOY494k7Q3xrs_w",
             "rawId": "9y1xA8Tmg1FEmT-c7_fvWZ_uoTuoih3OvR45_oAK-cwHWhAbXrl2q62iLVTjiyEZ7O7n-CROOY494k7Q3xrs_w",
             "response": {
@@ -112,7 +108,6 @@ class TestVerifyRegistrationResponse(TestCase):
                 "usb"
             ]
         }"""
-        )
 
         challenge = base64url_to_bytes(
             "TwN7n4WTyGKLc4ZY-qGsFqKnHM4nglqsyV0ICJlN2TO9XiRyFtrkaDwUvsql-gkLJXP6fnF1MlrZ53Mm4R7Cvw"
@@ -133,8 +128,7 @@ class TestVerifyRegistrationResponse(TestCase):
 
     def test_raises_when_root_cert_invalid_for_response(self) -> None:
         # "packed"
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "syGQPDZRUYdb4m3rdWeyPaIMYlbmydGp1TP_33vE_lqJ3PHNyTd0iKsnKr5WjnCcBzcesZrDEfB_RBLFzU3k4w",
             "rawId": "syGQPDZRUYdb4m3rdWeyPaIMYlbmydGp1TP_33vE_lqJ3PHNyTd0iKsnKr5WjnCcBzcesZrDEfB_RBLFzU3k4w",
             "response": {
@@ -147,9 +141,7 @@ class TestVerifyRegistrationResponse(TestCase):
                 "nfc",
                 "usb"
             ]
-        }
-        """
-        )
+        }"""
         challenge = base64url_to_bytes(
             "8LBCiOY3q1cBZHFAWtS4AZZChzGphy67lK7I70zKi4yC7pgrQ2Pch7nAjLk1wq9greshIAsW2AjibhXjjI0TmQ"
         )
@@ -169,8 +161,7 @@ class TestVerifyRegistrationResponse(TestCase):
             )
 
     def test_verifies_registration_over_cable(self) -> None:
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "9y1xA8Tmg1FEmT-c7_fvWZ_uoTuoih3OvR45_oAK-cwHWhAbXrl2q62iLVTjiyEZ7O7n-CROOY494k7Q3xrs_w",
             "rawId": "9y1xA8Tmg1FEmT-c7_fvWZ_uoTuoih3OvR45_oAK-cwHWhAbXrl2q62iLVTjiyEZ7O7n-CROOY494k7Q3xrs_w",
             "response": {
@@ -183,7 +174,6 @@ class TestVerifyRegistrationResponse(TestCase):
                 "cable"
             ]
         }"""
-        )
 
         challenge = base64url_to_bytes(
             "TwN7n4WTyGKLc4ZY-qGsFqKnHM4nglqsyV0ICJlN2TO9XiRyFtrkaDwUvsql-gkLJXP6fnF1MlrZ53Mm4R7Cvw"
@@ -199,3 +189,35 @@ class TestVerifyRegistrationResponse(TestCase):
         )
 
         assert verification.fmt == AttestationFormat.NONE
+
+    def test_supports_already_parsed_credential(self) -> None:
+        parsed_credential = parse_registration_credential_json(
+            """{
+                "id": "9y1xA8Tmg1FEmT-c7_fvWZ_uoTuoih3OvR45_oAK-cwHWhAbXrl2q62iLVTjiyEZ7O7n-CROOY494k7Q3xrs_w",
+                "rawId": "9y1xA8Tmg1FEmT-c7_fvWZ_uoTuoih3OvR45_oAK-cwHWhAbXrl2q62iLVTjiyEZ7O7n-CROOY494k7Q3xrs_w",
+                "response": {
+                    "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAFwAAAAAAAAAAAAAAAAAAAAAAQPctcQPE5oNRRJk_nO_371mf7qE7qIodzr0eOf6ACvnMB1oQG165dqutoi1U44shGezu5_gkTjmOPeJO0N8a7P-lAQIDJiABIVggSFbUJF-42Ug3pdM8rDRFu_N5oiVEysPDB6n66r_7dZAiWCDUVnB39FlGypL-qAoIO9xWHtJygo2jfDmHl-_eKFRLDA",
+                    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiVHdON240V1R5R0tMYzRaWS1xR3NGcUtuSE00bmdscXN5VjBJQ0psTjJUTzlYaVJ5RnRya2FEd1V2c3FsLWdrTEpYUDZmbkYxTWxyWjUzTW00UjdDdnciLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9"
+                },
+                "type": "public-key",
+                "clientExtensionResults": {},
+                "transports": [
+                    "cable"
+                ]
+            }"""
+        )
+
+        challenge = base64url_to_bytes(
+            "TwN7n4WTyGKLc4ZY-qGsFqKnHM4nglqsyV0ICJlN2TO9XiRyFtrkaDwUvsql-gkLJXP6fnF1MlrZ53Mm4R7Cvw"
+        )
+        rp_id = "localhost"
+        expected_origin = "http://localhost:5000"
+
+        verification = verify_registration_response(
+            credential=parsed_credential,
+            expected_challenge=challenge,
+            expected_origin=expected_origin,
+            expected_rp_id=rp_id,
+        )
+
+        assert verification.fmt == AttestationFormat.NONE
diff --git a/tests/test_verify_registration_response_android_key.py b/tests/test_verify_registration_response_android_key.py
index 215bc90..4608d38 100644
--- a/tests/test_verify_registration_response_android_key.py
+++ b/tests/test_verify_registration_response_android_key.py
@@ -2,7 +2,7 @@ from unittest import TestCase
 from unittest.mock import MagicMock, patch
 
 from webauthn.helpers import base64url_to_bytes
-from webauthn.helpers.structs import AttestationFormat, RegistrationCredential
+from webauthn.helpers.structs import AttestationFormat
 from webauthn import verify_registration_response
 
 
@@ -15,8 +15,7 @@ class TestVerifyRegistrationResponseAndroidKey(TestCase):
         # Mocked because response is from FIDO Conformance with unknown root certs
         mock_verify_certificate.return_value = True
 
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "V51GE29tGbhby7sbg1cZ_qL8V8njqEsXpAnwQBobvgw",
             "rawId": "V51GE29tGbhby7sbg1cZ_qL8V8njqEsXpAnwQBobvgw",
             "response": {
@@ -25,9 +24,8 @@ class TestVerifyRegistrationResponseAndroidKey(TestCase):
             },
             "type": "public-key",
             "clientExtensionResults": {}
-        }
-        """
-        )
+        }"""
+
         challenge = base64url_to_bytes("4ab7dfd1-a695-4777-985f-ad2993828e99")
         rp_id = "dev.dontneeda.pw"
         expected_origin = "https://dev.dontneeda.pw"
diff --git a/tests/test_verify_registration_response_android_safetynet.py b/tests/test_verify_registration_response_android_safetynet.py
index dbe9cd8..9eaa7f7 100644
--- a/tests/test_verify_registration_response_android_safetynet.py
+++ b/tests/test_verify_registration_response_android_safetynet.py
@@ -1,8 +1,7 @@
 from unittest import TestCase
 from unittest.mock import MagicMock, patch
 
-from webauthn.helpers import parse_attestation_object
-from webauthn.helpers.structs import RegistrationCredential
+from webauthn.helpers import parse_attestation_object, parse_registration_credential_json
 from webauthn.registration.formats.android_safetynet import (
     verify_android_safetynet,
 )
@@ -18,7 +17,7 @@ class TestVerifyRegistrationResponseAndroidSafetyNet(TestCase):
         # Mocked because these certs actually expired and started failing this test
         mock_verify_certificate.return_value = True
 
-        credential = RegistrationCredential.parse_raw(
+        credential = parse_registration_credential_json(
             """{
             "id": "AePltP2wAoNYwG5XGc9sfleGgDxQRHdkX8vphNIHv3HylIj_nZo9ncs7bLL65AGmVAc69pS4l64hgOBJU9o2jCQ",
             "rawId": "AePltP2wAoNYwG5XGc9sfleGgDxQRHdkX8vphNIHv3HylIj_nZo9ncs7bLL65AGmVAc69pS4l64hgOBJU9o2jCQ",
diff --git a/tests/test_verify_registration_response_apple.py b/tests/test_verify_registration_response_apple.py
index 5e1d203..dcb997f 100644
--- a/tests/test_verify_registration_response_apple.py
+++ b/tests/test_verify_registration_response_apple.py
@@ -2,7 +2,7 @@ from unittest import TestCase
 from unittest.mock import MagicMock, patch
 
 from webauthn.helpers import base64url_to_bytes
-from webauthn.helpers.structs import AttestationFormat, RegistrationCredential
+from webauthn.helpers.structs import AttestationFormat
 from webauthn import verify_registration_response
 
 
@@ -16,8 +16,7 @@ class TestVerifyRegistrationResponseApple(TestCase):
         # Mocked because these certs actually expired and started failing this test
         mock_verify_certificate.return_value = True
 
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "0yhsKG_gCzynIgNbvXWkqJKL8Uc",
             "rawId": "0yhsKG_gCzynIgNbvXWkqJKL8Uc",
             "response": {
@@ -26,9 +25,7 @@ class TestVerifyRegistrationResponseApple(TestCase):
             },
             "type": "public-key",
             "clientExtensionResults": {}
-        }
-        """
-        )
+        }"""
         challenge = base64url_to_bytes(
             "1nhqyMkfGAQL-tTcsfpuoyq8hqeoHr0d9tDmjv1BuJ97YTA3FLWS5EdY4qUg-Mzruc2siBdyVlnFIPB1g0Hh2A"
         )
diff --git a/tests/test_verify_registration_response_fido_u2f.py b/tests/test_verify_registration_response_fido_u2f.py
index 06be8e1..2b078e0 100644
--- a/tests/test_verify_registration_response_fido_u2f.py
+++ b/tests/test_verify_registration_response_fido_u2f.py
@@ -1,14 +1,13 @@
 from unittest import TestCase
 
 from webauthn.helpers import base64url_to_bytes
-from webauthn.helpers.structs import AttestationFormat, RegistrationCredential
+from webauthn.helpers.structs import AttestationFormat
 from webauthn import verify_registration_response
 
 
 class TestVerifyRegistrationResponseFIDOU2F(TestCase):
     def test_verify_attestation_from_yubikey_firefox(self) -> None:
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "lrjqbPdLbWXTJ2sFIreka9aWd2ED-SDx_VAgBAh4XmCJgjCjudEjoi42pGQd-_Bi6nNPQ3T7-xOEgty2I3m7cw",
             "rawId": "lrjqbPdLbWXTJ2sFIreka9aWd2ED-SDx_VAgBAh4XmCJgjCjudEjoi42pGQd-_Bi6nNPQ3T7-xOEgty2I3m7cw",
             "response": {
@@ -17,9 +16,7 @@ class TestVerifyRegistrationResponseFIDOU2F(TestCase):
             },
             "type": "public-key",
             "clientExtensionResults": {}
-        }
-        """
-        )
+        }"""
         challenge = base64url_to_bytes(
             "ZJVNlmOrXwwgQkd1gDMly8BIiIMV4IQDDsCr6KPbCIHcOZ5waKHm-MZtuY748SCqg-NZVpRrYFyFrc36gQoFtw"
         )
@@ -39,8 +36,7 @@ class TestVerifyRegistrationResponseFIDOU2F(TestCase):
         )
 
     def test_verify_attestation_from_fido_conformance(self) -> None:
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "2i53XtAuBVv2ztu9hdTkG_I4_zc-MhmYOjM2HWDCIlk",
             "rawId": "2i53XtAuBVv2ztu9hdTkG_I4_zc-MhmYOjM2HWDCIlk",
             "response": {
@@ -49,9 +45,7 @@ class TestVerifyRegistrationResponseFIDOU2F(TestCase):
             },
             "type": "public-key",
             "transports": null
-        }
-        """
-        )
+        }"""
         challenge = base64url_to_bytes(
             "c0WxorWwEToOxlbb5MsDZPWKdNiUhyOJL8s-MIcjI-Q5SFJOnoWe_q2ZhgYKHykv4PGw3L-_plgNBnKgww3dAw"
         )
@@ -72,8 +66,7 @@ class TestVerifyRegistrationResponseFIDOU2F(TestCase):
 
     def test_verify_attestation_with_unsupported_token_binding_status(self) -> None:
         # Credential contains `clientDataJSON: { tokenBinding: { status: "not-supported" } }`
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "JeC3qgQjIVysq88GxhGUYyDl4oZeW8mLWd7luJWQvnrm-wxGZ5mzf2bBCaUDq7D2qr4aQezvzfoFIF880ciAsQ",
             "rawId": "JeC3qgQjIVysq88GxhGUYyDl4oZeW8mLWd7luJWQvnrm-wxGZ5mzf2bBCaUDq7D2qr4aQezvzfoFIF880ciAsQ",
             "response": {
@@ -81,9 +74,7 @@ class TestVerifyRegistrationResponseFIDOU2F(TestCase):
                 "attestationObject": "o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAJB-eYnY106PNDhEVM8QLuZnVCxBX0Khp9vzdj-kjw1CAiEA0PlEdDtH4GKG3eY_1YC4sIZR2ZPg-9SsLgODDWUDYnRjeDVjgVkCUzCCAk8wggE3oAMCAQICBDxoKU0wDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMDExLzAtBgNVBAMMJll1YmljbyBVMkYgRUUgU2VyaWFsIDIzOTI1NzM0ODExMTE3OTAxMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvd9nk9t3lMNQMXHtLE1FStlzZnUaSLql2fm1ajoggXlrTt8rzXuSehSTEPvEaEdv_FeSqX22L6Aoa8ajIAIOY6M7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAKrADVEJfuwVpIazebzEg0D4Z9OXLs5qZ_ukcONgxkRZ8K04QtP_CB5x6olTlxsj-SXArQDCRzEYUgbws6kZKfuRt2a1P-EzUiqDWLjRILSr-3_o7yR7ZP_GpiFKwdm-czb94POoGD-TS1IYdfXj94mAr5cKWx4EKjh210uovu_pLdLjc8xkQciUrXzZpPR9rT2k_q9HkZhHU-NaCJzky-PTyDbq0KKnzqVhWtfkSBCGw3ezZkTS-5lrvOKbIa24lfeTgu7FST5OwTPCFn8HcfWZMXMSD_KNU-iBqJdAwTLPPDRoLLvPTl29weCAIh-HUpmBQd0UltcPOrA_LFvAf61oYXV0aERhdGFYxEvYNAXQphWPDADgZiwL4n_m5OggKcxKctshspIdTVKXQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEAl4LeqBCMhXKyrzwbGEZRjIOXihl5byYtZ3uW4lZC-eub7DEZnmbN_ZsEJpQOrsPaqvhpB7O_N-gUgXzzRyICxpQECAyYgASFYIJH525xGEk1oYXXxejoHvYcBrIwDbwcC_Kptwg6m5hArIlggvPFJ43EBPjkPK7aCIkiA3mtk_b6mkmzR8C_Fgjq3-xQ"
             },
             "type": "public-key"
-        }
-        """
-        )
+        }"""
         challenge = base64url_to_bytes(
             "aGFGCbL0VkPf1FcCVnSAZp4ZUJTcfF9v",
         )
@@ -104,8 +95,7 @@ class TestVerifyRegistrationResponseFIDOU2F(TestCase):
 
     def test_verify_attestation_with_unsupported_token_binding(self) -> None:
         # Credential contains `clientDataJSON: { tokenBinding: "unused" }`
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "jXFBv0gxr-DvGP58Oz3qfxMydiZM2RFlRoItoHyeAhdLNbmR7aPkzPVSKWO9VOZ4A2EEUQz8nsLtsP5EOqeNiQ",
             "rawId": "jXFBv0gxr-DvGP58Oz3qfxMydiZM2RFlRoItoHyeAhdLNbmR7aPkzPVSKWO9VOZ4A2EEUQz8nsLtsP5EOqeNiQ",
             "response": {
@@ -113,9 +103,7 @@ class TestVerifyRegistrationResponseFIDOU2F(TestCase):
                 "attestationObject": "o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEYwRAIgZNXeJymCt3MaODbi40dqksHQmfjvOajH4Th3qHK4nboCIEemlbhtg6g549fk5f6xU901qYeowKQ4lDOxwBisVo-RY3g1Y4FZAlMwggJPMIIBN6ADAgECAgQ8aClNMA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNVBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgwMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjAxMS8wLQYDVQQDDCZZdWJpY28gVTJGIEVFIFNlcmlhbCAyMzkyNTczNDgxMTExNzkwMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL3fZ5Pbd5TDUDFx7SxNRUrZc2Z1Gki6pdn5tWo6IIF5a07fK817knoUkxD7xGhHb_xXkql9ti-gKGvGoyACDmOjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS41MBMGCysGAQQBguUcAgEBBAQDAgUgMA0GCSqGSIb3DQEBCwUAA4IBAQCqwA1RCX7sFaSGs3m8xINA-GfTly7Oamf7pHDjYMZEWfCtOELT_wgeceqJU5cbI_klwK0AwkcxGFIG8LOpGSn7kbdmtT_hM1Iqg1i40SC0q_t_6O8ke2T_xqYhSsHZvnM2_eDzqBg_k0tSGHX14_eJgK-XClseBCo4dtdLqL7v6S3S43PMZEHIlK182aT0fa09pP6vR5GYR1PjWgic5Mvj08g26tCip86lYVrX5EgQhsN3s2ZE0vuZa7zimyGtuJX3k4LuxUk-TsEzwhZ_B3H1mTFzEg_yjVPogaiXQMEyzzw0aCy7z05dvcHggCIfh1KZgUHdFJbXDzqwPyxbwH-taGF1dGhEYXRhWMRL2DQF0KYVjwwA4GYsC-J_5uToICnMSnLbIbKSHU1Sl0EAAAAAAAAAAAAAAAAAAAAAAAAAAABAjXFBv0gxr-DvGP58Oz3qfxMydiZM2RFlRoItoHyeAhdLNbmR7aPkzPVSKWO9VOZ4A2EEUQz8nsLtsP5EOqeNiaUBAgMmIAEhWCD2BOHjcogovRtFtYbJpxctnRdgL-GL_Dwh0Pje7wUJ7yJYIACvXapVQbM5vEurQkI4096yU5dXv6CA-PftVTiTj-LT"
             },
             "type": "public-key"
-        }
-        """
-        )
+        }"""
         challenge = base64url_to_bytes(
             "B20LZQA6vmohW2pvCTi3ylUNGet856po",
         )
diff --git a/tests/test_verify_registration_response_packed.py b/tests/test_verify_registration_response_packed.py
index a23027a..9eaa41e 100644
--- a/tests/test_verify_registration_response_packed.py
+++ b/tests/test_verify_registration_response_packed.py
@@ -1,14 +1,13 @@
 from unittest import TestCase
 
 from webauthn.helpers import base64url_to_bytes
-from webauthn.helpers.structs import AttestationFormat, RegistrationCredential
+from webauthn.helpers.structs import AttestationFormat
 from webauthn import verify_registration_response
 
 
 class TestVerifyRegistrationResponsePacked(TestCase):
     def test_verify_attestation_from_yubikey_firefox(self) -> None:
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "syGQPDZRUYdb4m3rdWeyPaIMYlbmydGp1TP_33vE_lqJ3PHNyTd0iKsnKr5WjnCcBzcesZrDEfB_RBLFzU3k4w",
             "rawId": "syGQPDZRUYdb4m3rdWeyPaIMYlbmydGp1TP_33vE_lqJ3PHNyTd0iKsnKr5WjnCcBzcesZrDEfB_RBLFzU3k4w",
             "response": {
@@ -21,9 +20,7 @@ class TestVerifyRegistrationResponsePacked(TestCase):
                 "nfc",
                 "usb"
             ]
-        }
-        """
-        )
+        }"""
         challenge = base64url_to_bytes(
             "8LBCiOY3q1cBZHFAWtS4AZZChzGphy67lK7I70zKi4yC7pgrQ2Pch7nAjLk1wq9greshIAsW2AjibhXjjI0TmQ"
         )
@@ -43,8 +40,7 @@ class TestVerifyRegistrationResponsePacked(TestCase):
         )
 
     def test_verify_attestation_with_okp_public_key(self) -> None:
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "WlHiMqH6UhUs-d43z-aGlE3nsXuEOQpa9P9pwpqb4tmvtBMBfGvAV2wUrqBCDENjkkxd6kIRzZQKcluyOFlyW_vXVZSAEgod1xj-1QmFpuwyBVnlkQGefRbmUjbEt5iE4q3tdjy65EWIekO0SNjCQx3LxIJMzi25fgUkI9Y-gg0",
             "rawId": "WlHiMqH6UhUs-d43z-aGlE3nsXuEOQpa9P9pwpqb4tmvtBMBfGvAV2wUrqBCDENjkkxd6kIRzZQKcluyOFlyW_vXVZSAEgod1xj-1QmFpuwyBVnlkQGefRbmUjbEt5iE4q3tdjy65EWIekO0SNjCQx3LxIJMzi25fgUkI9Y-gg0",
             "response": {
@@ -56,9 +52,7 @@ class TestVerifyRegistrationResponsePacked(TestCase):
             "transports": [
                 "usb"
             ]
-        }
-        """
-        )
+        }"""
         challenge = base64url_to_bytes(
             "7JUBjWZFdFozulxb71DvHkh3P6WKUG4ElUo7wEkKic2JETJMAIKCf7rBE9YksI5oNjDzQ6Hqh6E73Oy6SPcMnw"
         )
diff --git a/tests/test_verify_registration_response_tpm.py b/tests/test_verify_registration_response_tpm.py
index d2392e2..2caf8dd 100644
--- a/tests/test_verify_registration_response_tpm.py
+++ b/tests/test_verify_registration_response_tpm.py
@@ -1,7 +1,7 @@
 from unittest import TestCase
 
 from webauthn.helpers import base64url_to_bytes
-from webauthn.helpers.structs import AttestationFormat, RegistrationCredential
+from webauthn.helpers.structs import AttestationFormat
 from webauthn import verify_registration_response
 
 
@@ -12,8 +12,7 @@ class TestVerifyRegistrationResponseTPM(TestCase):
         Mfgr Version: 500.5.0.0
         TPM Version: 2.0
         """
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "2O_TSbHXS3KJwx5uwajcqbKwWCBeHjOBCXXb7vrPfUU",
             "rawId": "2O_TSbHXS3KJwx5uwajcqbKwWCBeHjOBCXXb7vrPfUU",
             "response": {
@@ -21,9 +20,7 @@ class TestVerifyRegistrationResponseTPM(TestCase):
                 "attestationObject": "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"
             },
             "type": "public-key"
-        }
-        """
-        )
+        }"""
         challenge = base64url_to_bytes(
             "ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBZWFFpT2pFMk1qTXlOVGt3TWpNc0ltVjRjQ0k2TVRZeU16TTNPVEF5TXl3aWMzVmlJam9pZFhObGNtNWhiV1ZvWlhKbEluMC53amVIUjR4SnFGUS1SU0ZlX1dUVV9ORnhvN3dtQjByczVHMnFpRF9nVDNn"
         )
@@ -48,8 +45,7 @@ class TestVerifyRegistrationResponseTPM(TestCase):
         Mfgr Version: 1.3.2.8
         TPM Version: 2.0
         """
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "56iW7RC7YLiknnNU70kO5Bb-jip9-WTUbohh_Aqq1q4",
             "rawId": "56iW7RC7YLiknnNU70kO5Bb-jip9-WTUbohh_Aqq1q4",
             "response": {
@@ -57,9 +53,7 @@ class TestVerifyRegistrationResponseTPM(TestCase):
                 "attestationObject": "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"
             },
             "type": "public-key"
-        }
-        """
-        )
+        }"""
         challenge = base64url_to_bytes(
             "ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBZWFFpT2pFMk1qUTBOak00TVRrc0ltVjRjQ0k2TVRZeU5EVTRNemd4T1N3aWMzVmlJam9pWW05aUluMC5RaHVrZ1k1dEwyZHM4dXdBcnRLUGFrSzdEanpkZm04OXlvU3g0Zl9DV09F"
         )
@@ -84,8 +78,7 @@ class TestVerifyRegistrationResponseTPM(TestCase):
         Mfgr Version: 73.8.17568.5511
         TPM Version: 2.0
         """
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "kU6oEC95fTXAtpI6b2w69fQrKGntFFt1l_2ySjmndYM",
             "rawId": "kU6oEC95fTXAtpI6b2w69fQrKGntFFt1l_2ySjmndYM",
             "response": {
@@ -93,9 +86,7 @@ class TestVerifyRegistrationResponseTPM(TestCase):
                 "attestationObject": "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"
             },
             "type": "public-key"
-        }
-        """
-        )
+        }"""
         challenge = base64url_to_bytes(
             "ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBZWFFpT2pFMk1qUTBOelUwTXpjc0ltVjRjQ0k2TVRZeU5EVTVOVFF6Tnl3aWMzVmlJam9pY205c2JHbHVJbjAucnBZM2pUZUR0czA5TXNIbTJVbXltZzlZZHZvenc2clFPelF6MzVibHlmNA"
         )
@@ -115,8 +106,7 @@ class TestVerifyRegistrationResponseTPM(TestCase):
         )
 
     def test_verify_tpm_with_ecc_public_area_type(self) -> None:
-        credential = RegistrationCredential.parse_raw(
-            """{
+        credential = """{
             "id": "hsS2ywFz_LWf9-lC35vC9uJTVD3ZCVdweZvESUbjXnQ",
             "rawId": "hsS2ywFz_LWf9-lC35vC9uJTVD3ZCVdweZvESUbjXnQ",
             "response": {
@@ -124,9 +114,7 @@ class TestVerifyRegistrationResponseTPM(TestCase):
                 "attestationObject": "o2NmbXRjdHBtZ2F0dFN0bXSmY2FsZzn__mNzaWdZAQCqAcGoi2IFXCF5xxokjR5yOAwK_11iCOqt8hCkpHE9rW602J3KjhcRQzoFf1UxZvadwmYcHHMxDQDmVuOhH-yW-DfARVT7O3MzlhhzrGTNO_-jhGFsGeEdz0RgNsviDdaVP5lNsV6Pe4bMhgBv1aTkk0zx1T8sxK8B7gKT6x80RIWg89_aYY4gHR4n65SRDp2gOGI2IHDvqTwidyeaAHVPbDrF8iDbQ88O-GH_fheAtFtgjbIq-XQbwVdzQhYdWyL0XVUwGLSSuABuB4seRPkyZCKoOU6VuuQzfWNpH2Nl05ybdXi27HysUexgfPxihB3PbR8LJdi1j04tRg3JvBUvY3ZlcmMyLjBjeDVjglkFuzCCBbcwggOfoAMCAQICEGEZiaSlAkKpqaQOKDYmWPkwDQYJKoZIhvcNAQELBQAwQTE_MD0GA1UEAxM2RVVTLU5UQy1LRVlJRC1FNEE4NjY2RjhGNEM2RDlDMzkzMkE5NDg4NDc3ODBBNjgxMEM0MjEzMB4XDTIyMDExMjIyMTUxOFoXDTI3MDYxMDE4NTQzNlowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKo-7DHdiipZTzfA9fpTaIMVK887zM0nXAVIvU0kmGAsPpTYbf7dn1DAl6BhcDkXs2WrwYP02K8RxXWOF4jf7esMAIkr65zPWqLys8WRNM60d7g9GOADwbN8qrY0hepSsaJwjhswbNJI6L8vJwnnrQ6UWVCm3xHqn8CB2iSWNSUnshgTQTkJ1ZEdToeD51sFXUE0fSxXjyIiSAAD4tCIZkmHFVqchzfqUgiiM_mbbKzUnxEZ6c6r39ccHzbm4Ir-u62repQnVXKTpzFBbJ-Eg15REvw6xuYaGtpItk27AXVcEodfAylf7pgQPfExWkoMZfb8faqbQAj5x29mBJvlzj0CAwEAAaOCAeowggHmMA4GA1UdDwEB_wQEAwIHgDAMBgNVHRMBAf8EAjAAMG0GA1UdIAEB_wRjMGEwXwYJKwYBBAGCNxUfMFIwUAYIKwYBBQUHAgIwRB5CAFQAQwBQAEEAIAAgAFQAcgB1AHMAdABlAGQAIAAgAFAAbABhAHQAZgBvAHIAbQAgACAASQBkAGUAbgB0AGkAdAB5MBAGA1UdJQQJMAcGBWeBBQgDMFAGA1UdEQEB_wRGMESkQjBAMT4wEAYFZ4EFAgIMB05QQ1Q3NXgwFAYFZ4EFAgEMC2lkOjRFNTQ0MzAwMBQGBWeBBQIDDAtpZDowMDA3MDAwMjAfBgNVHSMEGDAWgBQ3yjAtSXrnaSNOtzy1PEXxOO1ZUDAdBgNVHQ4EFgQU1ml3H5Tzrs0Nev69tFNhPZnhaV0wgbIGCCsGAQUFBwEBBIGlMIGiMIGfBggrBgEFBQcwAoaBkmh0dHA6Ly9hemNzcHJvZGV1c2Fpa3B1Ymxpc2guYmxvYi5jb3JlLndpbmRvd3MubmV0L2V1cy1udGMta2V5aWQtZTRhODY2NmY4ZjRjNmQ5YzM5MzJhOTQ4ODQ3NzgwYTY4MTBjNDIxMy9lMDFjMjA2Mi1mYmRjLTQwYTUtYTQwZi1jMzc3YzBmNzY1MWMuY2VyMA0GCSqGSIb3DQEBCwUAA4ICAQAz-YGrj0S841gyMZuit-qsKpKNdxbkaEhyB1baexHGcMzC2y1O1kpTrpaH3I80hrIZFtYoA2xKQ1j67uoC6vm1PhsJB6qhs9T7zmWZ1VtleJTYGNZ_bYY2wo65qJHFB5TXkevJUVe2G39kB_W1TKB6g_GSwb4a5e4D_Sjp7b7RZpyIKHT1_UE1H4RXgR9Qi68K4WVaJXJUS6T4PHrRc4PeGUoJLQFUGxYokWIf456G32GwGgvUSX76K77pVv4Y-kT3v5eEJdYxlS4EVT13a17KWd0DdLje0Ae69q_DQSlrHVLUrADvuZMeM8jxyPQvDb7ETKLsSUeHm73KOCGLStcGQ3pB49nt3d9XdWCcUwUrmbBF2G7HsRgTNbj16G6QUcWroQEqNrBG49aO9mMZ0NwSn5d3oNuXSXjLdGBXM1ukLZ-GNrZDYw5KXU102_5VpHpjIHrZh0dXg3Q9eucKe6EkFbH65-O5VaQWUnR5WJpt6-fl_l0iHqHnKXbgL6tjeerCqZWDvFsOak05R-hosAoQs_Ni0EsgZqHwR_VlG86fsSwCVU3_sDKTNs_Je08ewJ_bbMB5Tq6k1Sxs8Aw8R96EwjQLp3z-Zva1myU-KerYYVDl5BdvgPqbD8Xmst-z6vrP3CJbtr8jgqVS7RWy_cJOA8KCZ6IS_75QT7Gblq6UGFkG7zCCBuswggTToAMCAQICEzMAAAbTtnznKsOrB-gAAAAABtMwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNDAeFw0yMTA2MTAxODU0MzZaFw0yNzA2MTAxODU0MzZaMEExPzA9BgNVBAMTNkVVUy1OVEMtS0VZSUQtRTRBODY2NkY4RjRDNkQ5QzM5MzJBOTQ4ODQ3NzgwQTY4MTBDNDIxMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJA7GLwHWWbn2H8DRppxQfre4zll1sgE3Wxt9DTYWt5-v-xKwCQb6z_7F1py7LMe58qLqglAgVhS6nEvN2puZ1GzejdsFFxz2gyEfH1y-X3RGp0dxS6UKwEtmksaMEKIRQn2GgKdUkiuvkaxaoznuExoTPyu0aXk6yFsX5KEDu9UZCgt66bRy6m3KIRnn1VK2frZfqGYi8C8x9Q69oGG316tUwAIm3ypDtv3pREXsDLYE1U5Irdv32hzJ4CqqPyau-qJS18b8CsjvgOppwXRSwpOmU7S3xqo-F7h1eeFw2tgHc7PEPt8MSSKeba8Fz6QyiLhgFr8jFUvKRzk4B41HFUMqXYawbhAtfIBiGGsGrrdNKb7MxISnH1E6yLVCQGGhXiN9U7V0h8Gn56eKzopGlubw7yMmgu8Cu2wBX_a_jFmIBHnn8YgwcRm6NvT96KclDHnFqPVm3On12bG31F7EYkIRGLbaTT6avEu9rL6AJn7Xr245Sa6dC_OSMRKqLSufxp6O6f2TH2g4kvT0Go9SeyM2_acBjIiQ0rFeBOm49H4E4VcJepf79FkljovD68imeZ5MXjxepcCzS138374Jeh7k28JePwJnjDxS8n9Dr6xOU3_wxS1gN5cW6cXSoiPGe0JM4CEyAcUtKrvpUWoTajxxnylZuvS8ou2thfH2PQlAgMBAAGjggGOMIIBijAOBgNVHQ8BAf8EBAMCAoQwGwYDVR0lBBQwEgYJKwYBBAGCNxUkBgVngQUIAzAWBgNVHSAEDzANMAsGCSsGAQQBgjcVHzASBgNVHRMBAf8ECDAGAQH_AgEAMB0GA1UdDgQWBBQ3yjAtSXrnaSNOtzy1PEXxOO1ZUDAfBgNVHSMEGDAWgBR6jArOL0hiF-KU0a5VwVLscXSkVjBwBgNVHR8EaTBnMGWgY6Bhhl9odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUUE0lMjBSb290JTIwQ2VydGlmaWNhdGUlMjBBdXRob3JpdHklMjAyMDE0LmNybDB9BggrBgEFBQcBAQRxMG8wbQYIKwYBBQUHMAKGYWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVFBNJTIwUm9vdCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAxNC5jcnQwDQYJKoZIhvcNAQELBQADggIBAFZTSitCISvll6i6rPUPd8Wt2mogRw6I_c-dWQzdc9-SY9iaIGXqVSPKKOlAYU2ju7nvN6AvrIba6sngHeU0AUTeg1UZ5-bDFOWdSgPaGyH_EN_l-vbV6SJPzOmZHJOHfw2WT8hjlFaTaKYRXxzFH7PUR4nxGRbWtdIGgQhUlWg5oo_FO4bvLKfssPSONn684qkAVierq-ly1WeqJzOYhd4EylgVJ9NL3YUhg8dYcHAieptDzF7OcDqffbuZLZUx6xcyibhWQcntAh7a3xPwqXxENsHhme_bqw_kqa-NVk-Wz4zdoiNNLRvUmCSL1WLc4JPsFJ08Ekn1kW7f9ZKnie5aw-29jEf6KIBt4lGDD3tXTfaOVvWcDbu92jMOO1dhEIj63AwQiDJgZhqnrpjlyWU_X0IVQlaPBg80AE0Y3sw1oMrY0XwdeQUjSpH6e5fTYKrNB6NMT1jXGjKIzVg8XbPWlnebP2wEhq8rYiDR31b9B9Sw_naK7Xb-Cqi-VQdUtknSjeljusrBpxGUx-EIJci0-dzeXRT5_376vyKSuYxA1Xd2jd4EknJLIAVLT3rb10DCuKGLDgafbsfTBxVoEa9hSjYOZUr_m3WV6t6I9WPYjVyhyi7fCEIG4JE7YbM4na4jg5q3DM8ibE8jyufAq0PfJZTJyi7c2Q2N_9NgnCNwZ3B1YkFyZWFYdgAjAAsABAByACCd_8vzbDg65pn7mGjcbcuJ1xU4hL4oA5IsEkFYv60irgAQABAAAwAQACAek7g2C8TeORRoKxuN7HrJ5OinVGuHzEgYODyUsF9D1wAggXPPXn-Pm_4IF0c4XVaJjmHO3EB2KBwdg_L60N0IL9xoY2VydEluZm9Yof9UQ0eAFwAiAAvQNGTLa2wT6u8SKDDdwkgaq5Cmh6jcD_6ULvM9ZmvdbwAUtMInD3WtGSdWHPWijMrW_TfYo-gAAAABPuBems3Sywu4aQsGAe85iOosjtXIACIAC5FPRiZSJzjYMNnAz9zFtM62o57FJwv8F5gNEcioqhHwACIACyVXxq1wZhDsqTqdYr7vQUUJ3vwWVrlN0ZQv5HFnHqWdaGF1dGhEYXRhWKR0puqSE8mcL3SyJJKzIM9AJiqUwalQoDl_KSULYIQe8EUAAAAACJhwWMrcS4G24TDeUNy-lgAghsS2ywFz_LWf9-lC35vC9uJTVD3ZCVdweZvESUbjXnSlAQIDJiABIVggHpO4NgvE3jkUaCsbjex6yeTop1Rrh8xIGDg8lLBfQ9ciWCCBc89ef4-b_ggXRzhdVomOYc7cQHYoHB2D8vrQ3Qgv3A"
             },
             "type": "public-key"
-        }
-        """
-        )
+        }"""
         challenge = base64url_to_bytes("uzn9u0Tx-LBdtGgERsbkHRBjiUt5i2rvm2BBTZrWqEo")
         rp_id = "webauthn.io"
         expected_origin = "https://webauthn.io"