Webauthn

Latest version: v2.5.0

Safety actively analyzes 701868 Python packages for vulnerabilities to keep your Python projects secure.

Scan your dependencies

Page 1 of 5

2.5.0

**Changes:**

- A new `require_user_presence` argument has been added to `verify_registration_response()` to enable verification of WebAuthn responses generated through use of [conditional create](https://w3c.github.io/webauthn/#dom-clientcapability-conditionalcreate) where the `up` bit in `authData.flags` will be `False` ([236](https://github.com/duo-labs/py_webauthn/pull/236), h/t bschoenmaeckers)
- `verify_authentication_response()` has been updated to return `user_verified` as well to indicate whether or not the user performed user verification ([235](https://github.com/duo-labs/py_webauthn/pull/235), h/t ggirol-rc)
- Verification of `"android-key"` attestation statements has been modernized in light of Android's latest observable behavior ([240](https://github.com/duo-labs/py_webauthn/pull/240))
- Verification of `"android-safetynet"` attestation statements now enforces the `"basicIntegrity"` flag instead of the `"ctsProfileMatch"` flag when determining device integrity ([241](https://github.com/duo-labs/py_webauthn/pull/241))
- The list of known TPM manufacturers has been updated ([242](https://github.com/duo-labs/py_webauthn/pull/242))

2.4.0

**Changes:**

- An optional `hints` argument has been added to `generate_registration_options()` to specify one or more categories of authenticators for the browser to prioritize registration of. See `webauthn.helpers.structs.PublicKeyCredentialHint` for more information ([234](https://github.com/duo-labs/py_webauthn/pull/234))

2.3.0

**Changes:**

- The minimum supported version of Python has been bumped up to Python 3.9, with ongoing testing from Python 3.9 through Python 3.13. Dependencies have been updated as well, including upgrading to `cryptography==43.0.3` ([233](https://github.com/duo-labs/py_webauthn/pull/233), with thanks to ds-cbo)

2.2.0

**Changes:**

- All exceptions in `webauthn.helpers.exceptions` now subclass the new `webauthn.helpers.exceptions.WebAuthnException` base exception ([219](https://github.com/duo-labs/py_webauthn/issues/219), h/t bschoenmaeckers)
- Support has been added for the new `"smart-card"` transport ([221](https://github.com/duo-labs/py_webauthn/pull/221))

2.1.0

from webauthn.helpers import parse_registration_options_json

json_reg_options: dict = get_stored_registration_options(session_id)
parsed_reg_options: PublicKeyCredentialCreationOptions = parse_registration_options_json(
json_reg_options,
)


This same logic applies to calls to `PublicKeyCredentialRequestOptions.parse_obj()` - these calls can be replaced with the new `webauthn.helpers.parse_authentication_options_json()` in this release as well.

2.0.0

**Changes:**

- See **Breaking Changes** below

**Breaking Changes:**

- [Pydantic](https://docs.pydantic.dev/latest/) is no longer used by py_webauthn. If your project
calls any Pydantic-specific methods on classes provided by py_webauthn then you will need to
refactor those calls accordingly. Typical use of py_webauthn should not need any major refactor
related to this change, but please see **Breaking Changes** below ([195](https://github.com/duo-labs/py_webauthn/pull/195))
- `webauthn.helpers.generate_challenge()` now always generates 64 random bytes and no longer accepts any arguments. Refactor your existing calls to remove any arguments ([198](https://github.com/duo-labs/py_webauthn/pull/198))
- `webauthn.helpers.exceptions.InvalidClientDataJSONStructure` has been replaced by `webauthn.helpers.exceptions.InvalidJSONStructure` ([195](https://github.com/duo-labs/py_webauthn/pull/195))
- `webauthn.helpers.json_loads_base64url_to_bytes()` has been removed ([195](https://github.com/duo-labs/py_webauthn/pull/195))
- The `user_id` argument passed into `generate_registration_options()` is now `Optional[bytes]`
instead of a required `str` value. A random sequence of 64 bytes will be generated for `user_id`
if it is `None` ([197](https://github.com/duo-labs/py_webauthn/pull/197))
- There are a few options available to refactor existing calls:

Option 1: Use the `base64url_to_bytes()` helper

If you already store your WebAuthn user ID bytes as base64url-encoded strings then you can simply decode these strings to bytes using an included helper:

**Before:**
py
options = generate_registration_options(
...
user_id: "3ZPk1HGhX_cul7z5UydfZE_vgnUYkOVshDNcvI1ILyQ",
)


**After:**

py
from webauthn.helpers import bytes_to_base64url

options = generate_registration_options(
...
user_id: bytes_to_base64url("3ZPk1HGhX_cul7z5UydfZE_vgnUYkOVshDNcvI1ILyQ"),
)


Option 2: Generate unique WebAuthn-specific identifiers for existing and new users

WebAuthn **strongly** encourages Relying Parties to use 64 randomized bytes for **every** user ID you pass into `navigator.credentials.create()`. This would be a second identifier used exclusively for WebAuthn that you associate along with your typical internal user ID.

py_webauthn includes a `generate_user_handle()` helper that can simplify the task of creating this special user identifier for your existing users in one go:

py
from webauthn.helpers import generate_user_handle

Pseudocode (imagine this is in some kind of migration script)
for user in get_all_users_in_db():
add_webauthn_user_id_to_db_for_user(
current_user=user.id,
webauthn_user_id=generate_user_handle(), Generates 64 random bytes
)


You can also use this method when creating new users to ensure that all subsequent users have a WebAuthn-specific identifier as well:

py
from webauthn.helpers import generate_user_handle

...existing user onboarding logic...

Pseudocode
create_new_user_in_db(
...
webauthn_user_id=generate_user_handle(),
)


Once your users are assigned their second WebAuthn-specific ID you can then pass those bytes into `generate_registration_options()` on subsequent calls:

py
Pseudocode
webauthn_user_id: bytes = get_webauthn_user_id_bytes_from_db(current_user.id)

options = generate_registration_options(
...
user_id=webauthn_user_id,
)


Option 3: Let `generate_registration_options()` generate a user ID for you

When the `user_id` argument is omitted then a random 64-byte identifier will be generated for you:

**Before:**
py
options = generate_registration_options(
...
user_id: "USERIDGOESHERE",
)


**After:**
py
Pseudocode
webauthn_user_id: bytes | None = get_webauthn_user_id_bytes_from_db(
current_user=current_user.id,
)

options = generate_registration_options(
...
user_id=webauthn_user_id,
)

if webauthn_user_id is None:
Pseudocode
store_webauthn_user_id_bytes_in_your_db(
current_user=current_user.id,
webauthn_user_id=options.user.id, Randomly generated 64-bytes
)


Option 4: Encode existing `str` argument to UTF-8 bytes

This technique is a quick win, but can be prone to base64url-related encoding and decoding quirks between browsers. **It is recommended you quickly follow this up with Option 2 or Option 3 above:**

**Before:**
py
options = generate_registration_options(
...
user_id: "USERIDGOESHERE",
)


**After:**

py
options = generate_registration_options(
...
user_id: "USERIDGOESHERE".encode('utf-8'),
)

Page 1 of 5

© 2025 Safety CLI Cybersecurity Inc. All Rights Reserved.