Skip to content

fix(auth): replace pyu2f reauth fallback with fido2#17391

Draft
goutamadwant wants to merge 2 commits into
googleapis:mainfrom
goutamadwant:fix-google-auth-reauth-pyu2f
Draft

fix(auth): replace pyu2f reauth fallback with fido2#17391
goutamadwant wants to merge 2 commits into
googleapis:mainfrom
goutamadwant:fix-google-auth-reauth-pyu2f

Conversation

@goutamadwant
Copy link
Copy Markdown

@goutamadwant goutamadwant commented Jun 8, 2026

Fix details :

Replaces the deprecated pyu2f security-key fallback in google-auth reauth with fido2, while keeping the WebAuthn handler path preferred when available.

The fallback keeps the existing U2F challenge response shape and covers the same main outcomes: valid assertion, ineligible key, no key, timeout, and missing optional dependency.


Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

Fixes #17381

@goutamadwant goutamadwant requested review from a team as code owners June 8, 2026 06:40
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request replaces the pyu2f dependency with fido2 for security key reauthentication, updating the challenge implementation, credentials documentation, setup dependencies, and associated tests. Feedback from the review highlights that fido2 version 2.0.0 does not exist on PyPI, so the dependency range in setup.py should target 1.x instead. Additionally, it is recommended to handle potential OSError exceptions when listing devices or communicating with the security key to prevent unexpected crashes and improve error handling.

Comment thread packages/google-auth/setup.py
Comment on lines +154 to +157
devices = list(CtapHidDevice.list_devices())
if not devices:
sys.stderr.write("No security key found.\n")
return None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid broad except Exception: blocks that silently return None. Instead, catch specific exceptions like OSError and log the exception using logger.warning to aid in debugging and prevent masking underlying issues.

Suggested change
devices = list(CtapHidDevice.list_devices())
if not devices:
sys.stderr.write("No security key found.\n")
return None
try:
devices = list(CtapHidDevice.list_devices())
except OSError as caught_exc:
logger.warning("Failed to list security keys: %s", caught_exc)
return None
if not devices:
sys.stderr.write("No security key found.\n")
return None
References
  1. Avoid broad except Exception: blocks that silently return None. Instead, log the exception (e.g., using logger.warning) to aid in debugging and prevent masking underlying issues.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added handling for OSError around device enumeration so OS or HID permission failures do not bubble out of the reauth flow.

Also added a test for this path.

Comment on lines +181 to +205
try:
signature = Ctap1(device).authenticate(
client_param, app_param, key_handle
)
except ApduError as caught_exc:
if caught_exc.code == APDU.WRONG_DATA:
continue
if caught_exc.code == APDU.USE_NOT_SATISFIED:
sys.stderr.write(
"Timed out while waiting for security key touch.\n"
)
return None
raise
except CtapError as caught_exc:
if caught_exc.code in (
CtapError.ERR.TIMEOUT,
CtapError.ERR.ACTION_TIMEOUT,
CtapError.ERR.KEEPALIVE_CANCEL,
CtapError.ERR.OPERATION_DENIED,
):
sys.stderr.write(
"Timed out while waiting for security key touch.\n"
)
return None
raise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When communicating with a security key, Ctap1(device).authenticate(...) can raise an OSError (or platform-specific subclasses like PermissionError or FileNotFoundError) if the user lacks sufficient permissions (e.g., missing udev rules on Linux) or if the device is disconnected. Adding an except OSError block ensures these common communication issues are handled gracefully by writing to stderr and continuing to the next device, rather than crashing the application.

                    try:
                        signature = Ctap1(device).authenticate(
                            client_param, app_param, key_handle
                        )
                    except ApduError as caught_exc:
                        if caught_exc.code == APDU.WRONG_DATA:
                            continue
                        if caught_exc.code == APDU.USE_NOT_SATISFIED:
                            sys.stderr.write(
                                "Timed out while waiting for security key touch.\n"
                            )
                            return None
                        raise
                    except CtapError as caught_exc:
                        if caught_exc.code in (
                            CtapError.ERR.TIMEOUT,
                            CtapError.ERR.ACTION_TIMEOUT,
                            CtapError.ERR.KEEPALIVE_CANCEL,
                            CtapError.ERR.OPERATION_DENIED,
                        ):
                            sys.stderr.write(
                                "Timed out while waiting for security key touch.\n"
                            )
                            return None
                        raise
                    except OSError as caught_exc:
                        sys.stderr.write(
                            "Failed to communicate with security key: {}.\n".format(
                                caught_exc
                            )
                        )
                        continue

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled this too. If one security key raises OSError while authenticating, the fallback now writes the error and tries the next detected device.

Added coverage for the bad-device then good-device case.

return self._obtain_challenge_input_webauthn(metadata, webauthn_handler)
except Exception:
# Attempt pyu2f if exception in webauthn flow
# Attempt fido2 if exception in webauthn flow.
Copy link
Copy Markdown
Contributor

@parthea parthea Jun 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for proposing this! Migrating away from pyu2f to fido2 is absolutely the right direction long-term, especially since pyu2f is officially deprecated. I'm concerned about the risk of breaking users. How do you feel about keeping both pyu2f and fido2, with a plan to remove pyu2f at a later date?

@_helpers.copy_docstring(ReauthChallenge)
    def obtain_challenge_input(self, metadata):
        # 1. Try the primary Webauthn Handler first
        try:
            factory = webauthn_handler_factory.WebauthnHandlerFactory()
            webauthn_handler = factory.get_handler()
            if webauthn_handler is not None:
                sys.stderr.write("Please insert and touch your security key\n")
                return self._obtain_challenge_input_webauthn(metadata, webauthn_handler)
        except Exception:
            pass

        # 2. Try fido2 (Modern fallback)
        try:
            import fido2
            return self._obtain_challenge_input_fido2(metadata)
        except ImportError:
            pass

        # 3. Try pyu2f (Legacy fallback with deprecation warning)
        try:
            import pyu2f
            import warnings
            
            warnings.warn(
                "Support for pyu2f is deprecated and will be removed in a future release. "
                "Please switch to fido2 by installing `fido2` or `google-auth[reauth]`.",
                category=DeprecationWarning,
                stacklevel=2
            )
            return self._obtain_challenge_input_pyu2f(metadata)
        except ImportError:
            # If both libraries are completely missing, guide them to install the modern one
            raise exceptions.ReauthFailError(
                "fido2 dependency is required to use the Security Key reauth feature. "
                "It can be installed via `pip install fido2`."
            )

This is just an example. We can use the _get_fido2_classes function that you have


return self._obtain_challenge_input_fido2(metadata)

def _get_fido2_classes(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add type hints and a doc string to this function. Also applies to _obtain_challenge_input_fido2

@parthea parthea added kokoro:force-run Add this label to force Kokoro to re-run the tests. kokoro:run Add this label to force Kokoro to re-run the tests. labels Jun 8, 2026
@yoshi-kokoro yoshi-kokoro removed kokoro:run Add this label to force Kokoro to re-run the tests. kokoro:force-run Add this label to force Kokoro to re-run the tests. labels Jun 8, 2026
@parthea parthea marked this pull request as draft June 8, 2026 16:22
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.

google-auth: reauth: uses deprecated u2f module

3 participants