Skip to content

feat(auth): Add Firebase Phone Number Verification support with verify_token()#953

Closed
codeprakhar25 wants to merge 1 commit into
firebase:mainfrom
codeprakhar25:claude-phone-number-verification
Closed

feat(auth): Add Firebase Phone Number Verification support with verify_token()#953
codeprakhar25 wants to merge 1 commit into
firebase:mainfrom
codeprakhar25:claude-phone-number-verification

Conversation

@codeprakhar25
Copy link
Copy Markdown

Summary

This PR adds the firebase_admin.phone_number_verification module, implementing JWT verification for the Firebase Phone Number Verification (FPNV) service. It continues the work from PR #934.

Problem

Server-side SDKs had no way to verify FPNV JWTs issued to clients after phone number verification, making it impossible to trust client-supplied phone numbers without a round-trip to a Firebase REST endpoint.

Solution

Added a new phone_number_verification module following the exact patterns established by app_check.py:

  • verify_token(token, app=None) — module-level function that validates and decodes an FPNV JWT.
  • PhoneNumberVerificationToken — a dict subclass wrapping the decoded claims with convenience properties (phone_number, issuer, audience, exp, iat).
  • _PhoneNumberVerificationService — internal service class that manages a cached PyJWKClient (6-hour TTL) and performs header/claim validation.
  • _Validators — reusable string-validation utilities.

Key technical details

App Check Phone Number Verification
Algorithm RS256 ES256
JWKS URL firebaseappcheck.googleapis.com/v1/jwks fpnv.googleapis.com/v1beta/jwks
Issuer global (https://firebaseappcheck.googleapis.com/) project-scoped (https://fpnv.googleapis.com/projects/{id})
Audience projects/{id} same as issuer
Subject Firebase App ID E.164 phone number
Extra header check none kid must be present

The issuer is also passed to jwt.decode() so PyJWT validates the iss claim automatically, giving a precise InvalidIssuerError rather than a generic one.

Testing

Added tests/test_phone_number_verification.py with 32 unit tests:

  • PhoneNumberVerificationToken — property accessors, dict behaviour, missing-claim defaults.
  • _PhoneNumberVerificationService — missing project ID, all non-string/empty-string token inputs, every header validation branch (kid, typ, alg), every _decode_and_verify error path (expired, bad signature, wrong audience, wrong issuer, malformed token, None/non-string/empty sub).
  • Module-level verify_token() — round-trip success, malformed JWT, header rejection.
  • End-to-end real-crypto test — generates a live P-256 key pair, signs a JWT with it, mocks only the JWKS fetch_data call, and verifies the full decoding path.

All 32 tests pass and both files score 10.00/10 with pylint.

Context Sources Used

  • id: firebase-admin-python

Added firebase_admin/phone_number_verification.py with a module-level
verify_token() function and supporting PhoneNumberVerificationService.
The service fetches public keys from the FPNV JWKS endpoint and verifies
ES256-signed JWTs, validating the kid/typ/alg headers, issuer, audience,
expiry, and phone-number subject claim.

Also added tests/test_phone_number_verification.py with 32 unit tests
covering all validation paths, including an end-to-end test that signs a
real JWT with a generated EC key pair and mocks only the JWKS fetch.
@google-cla
Copy link
Copy Markdown

google-cla Bot commented May 29, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@wiz-9635d3485b
Copy link
Copy Markdown

Wiz Scan Summary

Scanner Findings
Vulnerability Finding Vulnerabilities -
Data Finding Sensitive Data -
Secret Finding Secrets -
IaC Misconfiguration IaC Misconfigurations -
SAST Finding SAST Findings 1 Info
Software Management Finding Software Management Findings -
Total 1 Info

View scan details in Wiz

To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension.

Copy link
Copy Markdown

@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 introduces the Firebase Phone Number Verification module (firebase_admin/phone_number_verification.py) along with its corresponding test suite (tests/test_phone_number_verification.py). The new module provides functionality to verify JWTs issued by the Firebase Phone Number Verification service. Feedback on the changes highlights two main improvements: explicitly enabling cache_jwk_set=True in PyJWKClient to ensure token caching works as intended and avoids excessive network requests, and refactoring the check_string validator method from a @classmethod to a @staticmethod since it does not access class-level state.

'or set the GOOGLE_CLOUD_PROJECT environment variable.')
self._expected_issuer = _ISSUER_PREFIX + self._project_id
# Cache JWKS for up to 6 hours (21600 seconds) to reduce network overhead.
self._jwks_client = PyJWKClient(_JWKS_URL, lifespan=21600)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

In PyJWT, the PyJWKClient constructor defaults cache_jwk_set to False. When cache_jwk_set is False, the client will fetch the JWK set from the network on every single call to get_signing_key_from_jwt(), completely ignoring the lifespan parameter.\n\nTo actually enable caching and prevent excessive network requests to the Google JWKS endpoint, you must explicitly pass cache_jwk_set=True.

Suggested change
self._jwks_client = PyJWKClient(_JWKS_URL, lifespan=21600)
self._jwks_client = PyJWKClient(_JWKS_URL, cache_jwk_set=True, lifespan=21600)

Comment on lines +234 to +235
@classmethod
def check_string(cls, label: str, value: Any):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The check_string method does not access any class-level state or call other class methods via cls. Therefore, it should be defined as a @staticmethod rather than a @classmethod to follow Python best practices and improve code clarity.

    @staticmethod\n    def check_string(label: str, value: Any):

@codeprakhar25
Copy link
Copy Markdown
Author

Closing — this PR was opened by mistake by an automated experiment harness and is not a genuine contribution. Apologies for the noise.

@codeprakhar25 codeprakhar25 deleted the claude-phone-number-verification branch May 29, 2026 11:13
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