firebase_phone_number_verification

A Flutter plugin for Firebase Phone Number Verification (Firebase PNV).

Firebase PNV is a faster and more secure method for verifying phone numbers. Instead of sending an SMS OTP, it obtains the phone number assigned to the SIM in the device directly from the carrier with a single tap. This:

  • reduces friction for the user (no code to receive and type),
  • improves reliability (no dependency on SMS delivery),
  • eliminates SMS phishing / OTP-interception attack vectors.

Platform support: Firebase PNV is currently available on Android only. On other platforms, isSupported() returns false so you can fall back to your existing verification method (for example SMS OTP).

Setup

1. Configure your Firebase project

  1. Add Firebase to your Android app if you haven't already (guide), including the google-services.json file and the com.google.gms.google-services Gradle plugin.
  2. In the Firebase console, complete the Phone Number Verification onboarding steps (under Security > Phone Verification).

2. Add the plugin

flutter pub add firebase_phone_number_verification

The plugin depends on the native com.google.firebase:firebase-pnv library; no extra Gradle configuration is needed.

Usage

import 'package:firebase_phone_number_verification/firebase_phone_number_verification.dart';

final fpnv = FirebasePhoneNumberVerification.instance;

You can try the full flow without a billing account or a real SIM:

  1. In the Firebase console, go to Security > Phone Verification > Testing and click Generate token.
  2. Enable a test session with the generated test number ID (valid for 7 days):
await fpnv.enableTestSession('YOUR_TEST_NUMBER_ID');

To use test mode, the device must be enrolled in the Google system services public beta program. Test tokens work on both physical devices and emulators.

Check for support

On app launch, check whether the device and its SIM(s) support Firebase PNV. This pre-check does not require user consent:

if (await fpnv.isSupported()) {
  // Show your Firebase PNV explainer screen, then start the flow.
} else {
  // Fall back to SMS OTP verification.
}

For per-SIM details use getVerificationSupportInfo():

final results = await fpnv.getVerificationSupportInfo();
for (final r in results) {
  print('SIM ${r.simSlot} (${r.carrierId}): supported=${r.isSupported}');
}

Verify the phone number

getVerifiedPhoneNumber() runs the entire end-to-end flow: it shows the system consent UI (Android Credential Manager), contacts the carrier through the Firebase PNV backend, and returns the verified phone number with a signed token:

try {
  final result = await fpnv.getVerifiedPhoneNumber();

  print(result.phoneNumber); // In test mode: valid country code + all zeros.
  print(result.token);       // Signed JWT — send this to your backend.
} on FirebasePnvException catch (e) {
  switch (e.code) {
    case FirebasePnvErrorCode.credentialManagerError:
      // The user declined consent or Credential Manager failed.
      break;
    case FirebasePnvErrorCode.carrierNotSupported:
      // Fall back to SMS OTP.
      break;
    default:
      // Handle other errors (network, integrity check, etc.).
  }
}

Tip: show a loading spinner before calling getVerifiedPhoneNumber() and dismiss it when the future completes, so the 1–3 second carrier round-trip feels responsive.

Verify the token on your backend

If you use the verified phone number outside the app client, pass the token (not the raw phone number) to your backend and verify its signature there. See Verify Firebase PNV tokens.

You can also use the token as part of a sign-in flow with Firebase Authentication (via custom auth) or your own authentication system.

Error codes

FirebasePnvException.code mirrors the native FirebasePnvStatusCodes:

Code Meaning
carrierNotSupported The carrier is not supported.
invalidDigitalCredentialResponse The digital credential response is invalid.
integrityCheckFailed Device integrity check failed.
preflightCheckFailed Preflight check failed.
unsupportedOperation The API is not supported on this device.
credentialManagerError Credential Manager error (includes user declining consent).
invalidTestNumberId The test number ID is invalid, expired, or not found.
testSessionAlreadyEnabled enableTestSession was called more than once.
activityContextRequired The flow was started without a foreground Activity.
noActivity The plugin is not attached to an Android Activity.
unsupportedPlatform Called on a non-Android platform.

Going to production

Once you're happy with the flow in test mode, follow the Upgrade to production guide (enable billing, complete OAuth brand verification, and remove the enableTestSession call).

Example

See the example app for a complete runnable demo.

License

MIT