flutter_digital_credentials 0.1.0-dev.3
flutter_digital_credentials: ^0.1.0-dev.3 copied to clipboard
Android Credential Manager Digital Credentials verifier bridge for Flutter.
flutter_digital_credentials #
Experimental Android-only Flutter bridge for Credential Manager Digital Credentials.
The plugin opens Android's system credential selector with an OpenID4VP request issued by your verifier backend, then returns the original presentation JSON. It does not cryptographically verify claims, create users, or issue a session. Send every presentation to a trusted backend for verification.
Status #
The AndroidX Digital Credentials API used by this package is experimental.
The current implementation compiles against androidx.credentials:1.7.0-alpha03.
This flow was verified end-to-end on a Motorola edge 60 fusion running Android
16 with the Google provider and the included local verifier. It remains an
experimental API integration, not production identity infrastructure.
System consent sheet #
This is the Android system consent sheet shown by the verified-email example on the tested Motorola device. The account photo, name, and email are redacted in black; all remaining UI is captured from the real provider flow.

Requirements #
- Android 6 / API 23 or later at runtime.
- A compatible Google Play services and Digital Credentials provider setup.
- A verifier backend that generates the OpenID4VP request, binds a single-use nonce, and verifies the returned presentation.
Usage #
final credentials = FlutterDigitalCredentials();
// Fetch this JSON from your backend. Do not generate a production nonce in the
// Flutter client.
final requestJson = await verifierApi.createVerifiedEmailRequest();
try {
final presentation = await credentials.request(requestJson: requestJson);
// Send the raw presentation to the same trusted backend. It must verify the
// issuer, SD-JWT signature, key binding, audience/origin, nonce, expiry and
// application claim policy before creating a session.
await verifierApi.completeVerifiedEmail(presentation.credentialJson);
} on DigitalCredentialNotFoundException {
// Offer OTP or magic-link fallback.
} on DigitalCredentialCancelledException {
// The user dismissed the system flow.
}
DigitalCredentialPresentation.credentialJson is unverified transport data.
Protocol models #
Use OpenId4VpRequest, DcqlCredentialQuery, and DcqlClaimQuery when your
backend needs Dart to serialize an already-authorized request. The backend must
still generate the nonce and own verifier policy.
final request = OpenId4VpRequest(
nonce: serverNonce,
credentials: [
DcqlCredentialQuery(
id: 'credential',
format: 'dc+sd-jwt',
meta: const {'vct_values': ['ExampleCredential']},
),
],
);
final presentation = await credentials.requestOpenId4Vp(request: request);
final response = presentation.parseOpenId4VpResponse();
final token = response.presentationsFor('credential').single;
The parser supports standard, legacy, and digital-wrapped response envelopes.
It throws DigitalCredentialProtocolException for a protocol error or malformed
envelope. Parsed tokens remain opaque and unverified.
Age-gate request (18+) #
OpenId4VpRequest.mdlAgeOver18 requests the selective-disclosure
age_over_18 predicate from an ISO mDL credential. It deliberately does not
request a birth date, name, address, or licence number.
final request = OpenId4VpRequest.mdlAgeOver18(nonce: serverNonce);
final presentation = await credentials.requestOpenId4Vp(request: request);
// The backend must validate this as a trusted issuer's mDL response before
// allowing access to an age-gated feature.
await verifierApi.completeAgeGate(presentation.credentialJson);
The helper does not provide a wallet, an issuer, regional mDL availability, or an age-verification decision. The Android API supports requests for age attributes; each product must obtain an appropriate issuer and implement its own server-side trust and access policy.
Phone-number verification #
Phone verification uses a carrier-issued TS.43 credential. It is available on Android 10 / API 29 and later, and requires an account with a phone-verification aggregator or carrier. The aggregator gives the backend an opaque DCQL credential object and authorization JWT; the backend gives both that object and its own nonce to Flutter.
final request = Ts43PhoneNumberVerificationRequest.fromAggregatorDcqlJson(
nonce: serverNonce,
aggregatorDcqlCredentialJson: aggregatorDcqlJson,
purpose: Ts43PhoneVerificationPurpose.retrievePhoneNumber,
);
final presentation = await credentials.request(requestJson: request.toJson());
Use the raw presentation only on the backend: validate the TS.43 response, then exchange it with the same aggregator for the verified phone number. Flutter must not log or persist the authorization JWT, TS.43 token, or returned number. The package has no Firebase dependency. Firebase Phone Number Verification can be used as an aggregator integration outside this plugin.
Firebase PNV test-mode example #
The bundled Android example includes an optional Firebase PNV bridge for
device-level E2E testing. It is not part of the plugin API or dependency graph.
Register your own Firebase Android app, keep google-services.json outside
source control, enable PNV Testing, and run:
cd example
flutter run --dart-define=FIREBASE_PNV_TEST_TOKEN=your-seven-day-test-token
The example sends Firebase's signed token to the local verifier, which checks
Firebase's ES256 JWKS, issuer, audience, expiry, and sub. Never treat a
client-returned phone number as verified.
API reference #
| Member | Description |
|---|---|
FlutterDigitalCredentials.request |
Opens Android Credential Manager with server-supplied request JSON and returns the raw presentation. |
FlutterDigitalCredentials.requestOpenId4Vp |
Serializes a typed OpenID4VP/DCQL request, then opens Android Credential Manager. |
DigitalCredentialPresentation.credentialJson |
Opaque, unverified provider response. Send it unchanged to the verifier. |
Runnable verified-email example #
The included example is a full local verifier flow: it obtains a one-time
OpenID4VP request from tool/verified_email_verifier, opens Credential
Manager, and posts the raw presentation back for server-side verification.
Never use decoded client-side claims as proof of identity or account ownership.
To run it on a USB-connected Android device:
cd tool/verified_email_verifier
npm install
npm start
# In another terminal, at the repository root:
adb reverse tcp:8787 tcp:8787
cd example
flutter run
The debug app connects to http://127.0.0.1:8787 through adb reverse.
This cleartext route exists only in the debug manifest. See the
verifier guide for its local-only
security boundaries.
Backend contract #
The companion architecture guide describes a reference TypeScript verifier with a short-lived, single-use verification session:
POST /verification-sessionsreturnssessionIdand OpenID4VP request JSON.- Flutter calls
request()and posts the raw response toPOST /verification-sessions/{id}/complete. - The backend validates the presentation before returning an account result.
The package is backend-neutral. The reference architecture chooses Node because
Android's verified-email guide explicitly recommends @sd-jwt/sd-jwt-vc for
server-side verification. Android implementation guide
Errors #
The public API exposes typed exceptions:
DigitalCredentialsUnsupportedExceptionDigitalCredentialNotFoundExceptionDigitalCredentialCancelledExceptionDigitalCredentialInvalidRequestExceptionDigitalCredentialInterruptedExceptionDigitalCredentialProviderExceptionDigitalCredentialProtocolExceptionDigitalCredentialUnknownException
Messages are deliberately sanitized and do not include credential material.
Example #
The Flutter example supports verified email, an mDL 18+ request, and an optional Firebase PNV test flow; it never renders raw credential JSON.
Scope #
This version supports verifier retrieval, generic DCQL/OpenID4VP protocol models, and construction of a selective-disclosure ISO mDL 18+ request. Credential holder/wallet registration, issuance, phone-number exchange, government IDs, age-policy verification, and Firebase Auth are not included.
Verified email is useful for signup or low-risk re-verification. It is not a universal account-recovery factor: an email claim does not always establish inbox freshness or satisfy a product's recovery policy.