Generated banner for Verified Email
Generated logo for Verified Email

Verified Email

Android (Google)OfficialCreated by Google LLCDistributed by Android (Google)

  • Skill
  • Tool integration
  • Software development
  • Apache-2.0
  • No issues found

From the author

Provides a complete workflow for implementing verified email retrieval on Android Credential Manager API. Use this skill to integrate a secure, OTP-less email verification flow into an Android app. This skill solves the problem of high-friction sign-up processes by leveraging cryptographically verified credentials from trusted providers like Google.
— the description by Google LLC

From the skill’s files

Taken from the skill’s own files (its description, section headings and stated usage). Not an AutoGineer summary, and not written by the author as one.

Provides a complete workflow for implementing verified email retrieval on Android Credential Manager API. Use this skill to integrate a secure, OTP-less email verification flow into an Android app. This skill solves the problem of high-friction sign-up processes by leveraging cryptographically verified credentials from trusted providers like Google. Email verification is applicable for the following use cases: Account Creation/Sign-up: Remove friction by skipping manual email verification.; Account Recovery: Securely verify email ownership during recovery flows.; Re-authentication: Versatile verification for high-risk actions, independent of the initial sign-in method..

What it does

  • Fundamentals
  • Codebase exploration for Use Cases
  • Identifying Integration Points
  • Important pointers for Implementation
  • Overview
  • Add dependencies

When to use it

  • Use this skill to integrate a secure, OTP-less email verification flow into an Android app.
  • Account Creation/Sign-up: Remove friction by skipping manual email verification.
  • Account Recovery: Securely verify email ownership during recovery flows.
  • Re-authentication: Versatile verification for high-risk actions, independent of the initial sign-in method.

Advantages

The skill’s files do not state this.

Usage examples

The skill’s files do not state this.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/android/verified-email/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip verified-email.zip -d .agents/skills

Documentation by Google LLC

Fundamentals

  • Overview of Digital Credentials: Learn about cryptographically verifiable documents and the role of Credential Manager.

  • Glossary : Definitions for dcql_query, UserInfoCredential, and GetDigitalCredentialOption.

Standards & Examples
Requirements
  • SDK Version: Minimum SDK 28 (Android 9) is required.

  • GMS Version: Google Play services version 25.49.x or higher.

Use Cases

Email verification is applicable for the following use cases:

  • Account Creation/Sign-up: Remove friction by skipping manual email verification.

  • Account Recovery: Securely verify email ownership during recovery flows.

  • Re-authentication: Versatile verification for high-risk actions, independent of the initial sign-in method.

Limitations & Nuances
  • Workspace Accounts: Google does not issue verifiable credentials for Google Workspace Accounts.

  • Freshness: For [email protected] addresses, Google verifies the email at account creation but there is no freshness claim; implement an additional challenge like an OTP.

Scope & Pre-requisites

Crucial : This skill focuses exclusively on the Android client-side integration . It does not implement the app's server-side cryptographic validation logic. Server-side validation of the returned credential is required for security and must be implemented in your backend.

Codebase exploration for Use Cases

Get started with the following queries in project source code to find relevant screens with different use cases to implement verified email:

  • SignUpScreen

  • "Email address"

  • "Recover Account"

  • "Account Recovery"

  • "Forgot password?"

  • "Delete Account"

Identifying Integration Points

To implement this feature effectively, you must first locate the relevant flows in your codebase. To initiate, start with the following strategies to cater to different use cases using verified email:

1. Search for Navigation Routes

If your app uses Navigation, search for routes or destinations related to authentication:

Look for:

  • Keywords : signup, registration, create_account, forgot_password, recovery, verify_email.

  • Code Pattern : Search for NavHost or composable destinations using these strings.

2. Locate Authentication ViewModels

Find the business logic handling user attributes and account creation, account recovery:

  • Keywords : SignUpViewModel, AuthViewModel, RegistrationRepository.

  • Code Pattern : Look for methods like onCreateAccount, onRecoverAccount, or validateEmail.

3. Find instances of reauthentication for sensitive actions

For reauthentication use cases, find areas where users perform sensitive actions:

  • Keywords : ChangePassword, UpdatePayment, DeleteAccount, UpdateDetails, EditUserDetails

Important pointers for Implementation

  • Construct a Digital Credential Request and present it to the user.

  • Make sure to follow the request JSON structure as mentioned in documentation.

  • While presenting the request to the user, check if result credential is DigitalCredential and credential.credentialJson as responseJsonString

  • Parse the response from the client.

  • Offer a passkey creation option if one is not already present.

  • Assume a local SdJwtParser to parse raw SD-JWT and return a JSONObject.

  • Use a VerifiedUserInfo data class to store the parsed name and email.

  • Leave a TODO for developers to handle the app's server-side validation and parsing.

  • Direct users to the home screen after API call success and show a snackbar with user details for reference purpose only.

Overview

This guide describes how to implement verified email retrieval using the Digital Credentials Verifier API through an OpenID for Verifiable Presentations (OpenID4VP) request.

Add dependencies

In your app's build.gradle file, add the following dependencies for Credential Manager:

Kotlin
dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha03")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")
}
Groovy
dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha03"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha03"
}

Initialize Credential Manager

Use your app or activity context to create a CredentialManager object.

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

Construct the Digital Credential request

To request a verified email, construct a GetCredentialRequest containing a GetDigitalCredentialOption. This option requires a requestJson string formatted as an OpenID for Verifiable Presentations (OpenID4VP) request.

The OpenID4VP request JSON must follow a specific structure. The current providers support a JSON structure with an outer "digital": {"requests": [...]} wrapper.

    val nonce = generateSecureRandomNonce()

    // This request follows the OpenID4VP spec
    val openId4vpRequest = """
{
  "requests": [
    {
      "protocol": "openid4vp-v1-unsigned",
      "data": {
        "response_type": "vp_token",
        "response_mode": "dc_api",
        "nonce": "$nonce",
        "dcql_query": {
          "credentials": [
            {
              "id": "user_info_query",
              "format": "dc+sd-jwt",
               "meta": { 
                  "vct_values": ["UserInfoCredential"] 
               },
              "claims": [ 
                {"path": ["email"]}, 
                {"path": ["name"]},  
                {"path": ["given_name"]},
                {"path": ["family_name"]},
                {"path": ["picture"]},
                {"path": ["hd"]},
                {"path": ["email_verified"]}
              ]
            }
          ]
        }
      }
    }
  ]
}
"""

    val getDigitalCredentialOption = GetDigitalCredentialOption(requestJson = openId4vpRequest)
    val request = GetCredentialRequest(listOf(getDigitalCredentialOption))

The request contains the following key information:

  • DCQL query : The dcql_query specifies the credential type and the claims being requested (email_verified). You can request other claims to determine the level of verification. A few possible claims are as follows:

    • email_verified: In the response, this is a Boolean that indicates whether the email is verified.

    • hd (hosted domain): In the response, this is empty.

    [!NOTE] Note: If email_verified is true and hd is empty in the response, it implies that the account is an authorized Google Account. Google does not issue verifiable credentials for Google Workspace Accounts. However, the hd field is present in verifiable credentials issued for non-workspace accounts. You are encouraged to implement handling this field to future-proof your app. If the email is [email protected], Google verified this email when the Google Account was created, but there is no freshness claim. Therefore, for non-Google emails, you should consider an additional challenge, such as an OTP, to verify the user. To understand the schema of the credential and the specific rules for validating fields like email_verified, refer to the Google Identity guides.

  • nonce: A unique, cryptographically secure random value is generated for each request. This is critical for security, as it prevents replay attacks.

  • UserInfoCredential: This value implies a specific type of digital credential that contains user attributes. Including this in the request is pivotal to distinguish the email verification use case.

Next, wrap the openId4vpRequest JSON in a GetDigitalCredentialOption, create a GetCredentialRequest, and call getCredential().

[!NOTE] Note: The hd and email_verified fields are hidden from users in Credential Manager's built-in UI. You cannot make a request with only these hidden fields- in case of such requests, the response is the GetCredentialCancellationException.

Present the request to the user

Present the user with the request, using the Credential Manager built-in UI.

coroutineScope {
    try {
        // Requesting Digital Credential from user...
        val result = credentialManager.getCredential(activity, request)

        when (val credential = result.credential) {
            is DigitalCredential -> {
                val responseJsonString = credential.credentialJson

                // Successfully received digital credential response.

                // Next, parse this response and send it to your server.
                // ...
            }

            else -> {
                // handle Unexpected State() - Up to the developer
            }
        }
    } catch (e: Exception) {
        // handle exceptions - Up to the developer
    }
}

[!NOTE] Note: There is no equivalent of Sign in with Google's preferImmediatelyAvailableCredentials for Digital Credentials. If no verifiable credential is found (for example, no eligible account on device), the user will be shown a "No options available" or similar system screen.

Parse the response on the client

[!WARNING] Warning: From August 2026, the response JSON format has been updated to strictly match the W3C standards. It contains data and protocol keys, with the OpenID4VP vp_token nested in data, while legacy formats hold the vp_token directly. Ensure your client-side parsing and server-side validation handle both formats during the transition period, while the older implementation is phased out. Apps that begin to integrate the email verification flow after August 2026 need to use the new format only.

After receiving the response, you can perform a preliminary parse on the client. This is useful for immediately updating the UI, for example, by showing the user's name.

[!IMPORTANT] Important: This step is not for validation. Full cryptographic verification must be performed on your server.

The following code extracts the raw Selective Disclosure JWT (SD-JWT) and uses a helper to decode its claims.

// 1. Parse the outer JSON wrapper to get the `vp_token`
val responseData = JSONObject(responseJsonString)
val dataObject = responseData.getJSONObject("data")
val vpToken = dataObject.getJSONObject("vp_token")

// 2. Extract the raw SD-JWT string
val credentialId = vpToken.keys().next()
val rawSdJwt = vpToken.getJSONArray(credentialId).getString(0)

// 3. Use your parser to get the verified claims
// Server-side validation/parsing is highly recommended.

// Assumes a local parser like the one in our SdJwtParser.kt sample
val claims = SdJwtParser.parse(rawSdJwt)
Log.d("TAG", "Parsed Claims: ${claims.toString(2)}")

// 4. Create your VerifiedUserInfo object with REAL data
val userInfo = VerifiedUserInfo(
    email = claims.getString("email"),
    displayName = claims.optString("name", claims.getString("email"))
)

Handle the response

The Credential Manager API will return a DigitalCredential response.

The following is an example of what the raw responseJsonString looks like, and what the claims look like after parsing the inner SD-JWT where you get additional metadata as well along with verified email:

/*
// Example of the raw JSON response from credential.credentialJson:
{
  "protocol": "openid4vp-v1-unsigned",
  "data": {
    "vp_token": {
      // This key matches the 'id' you set in your dcql_query
      "user_info_query": [
        // The SD-JWT string (Issuer JWT ~ Disclosures ~ Key Binding JWT)
        "eyJhbGciOiJ...~WyI...IiwgImVtYWlsIiwgInVzZXJAZXhhbXBsZS5jb20iXQ~...~eyJhbGciOiJ..."
      ]
    }
  }
}

// Example of the parsed and verified claims from the SD-JWT on your server:
{
  "cnf": {
    "jwk": {..}
  },
  "exp": 1775688222,
  "iat": 1775083422,
  "iss": "https://verifiablecredentials-pa.googleapis.com",
  "vct": "UserInfoCredential",
  "email": "[email protected]",
  "email_verified": true,
  "given_name": "Jane",
  "family_name": "Doe",
  "name": "Jane Doe",
  "picture": "http://example.com/janedoe/me.jpg",
  "hd": ""
}
 */

[!IMPORTANT] Important: We highly recommend that after receiving the verified email, you trigger Credential Manager's passkey creation.

Server-side validation for account creation

Since the retrieved email is cryptographically verified, you can omit the email OTP verification step, significantly reducing sign-up friction and potentially increasing conversion. This process is best handled on your server. The client sends the raw response (containing the vp_token) and the original nonce to a new server endpoint.

For verification, your application must send the full responseJsonString to your server for cryptographic validation before creating an account or logging the user in.

The digital credential provides two critical levels of verification for your server:

  • Authenticity of the data : Verifying the issuer (iss) URL and the SD-JWT signature proves that a trusted authority issued this data.

  • Identity of the presenter : Verifying the cnf field and the Key Binding (kb) signature confirms that the credential is being shared by the same device it was originally issued to, preventing it from being intercepted or used on another device.

The validation on the server must achieve the following:

[!NOTE] Note: Use a standard library (such as @sd-jwt/sd-jwt-vc for Node.js) to perform the verification steps as outlined in the OpenID for Verifiable Presentations specification.

For full security, make sure that you also validate the nonce to prevent replay attacks.

By combining these steps, your server can validate both the authenticity of the data and the identity of the presenter, ensuring the credential wasn't intercepted or spoofed before provisioning the new account.

[!WARNING] Warning: As mentioned in Parse the response on the client, from August 2026, the response JSON format has been updated to match W3C standards. Ensure your client-side parsing and server-side validation handle both formats during the transition period, while the older implementation is phased out. Apps that begin to integrate the email verification flow after August 2026 need to use the new format only.

try {
    // Send the raw credential response and the original nonce to your server.
    // Your server must validate the response. createAccountWithVerifiedCredentials
    // is a custom implementation per each RP for server side verification and account creation.
    val serverResponse = createAccountWithVerifiedCredentials(responseJsonString, nonce)

    // Server returns the new account info (e.g., email, name)
    val claims = JSONObject(serverResponse.json)

    val userInfo = VerifiedUserInfo(
        email = claims.getString("email"),
        displayName = claims.optString("name", claims.getString("email"))
    )

    // handle response - Up to the developer
} catch (e: Exception) {
    // handle exceptions - Up to the developer
}

Passkey creation

An optional but highly recommended next step after provisioning an account is to immediately create a passkey for that account. This provides a secure, passwordless method for the user to sign in. This flow is identical to a standard passkey registration.

WebView support

For the flow to work on a WebView, developers should implement a JavaScript bridge (JS Bridge) to facilitate the handoff. This bridge allows the WebView object to signal the native app, which can then perform the actual call to the Credential Manager API.

See also

Critical Security Guidelines

To maintain the integrity of the email verification flow, the following security requirements are mandatory:

  • Server-side Validation : Never trust claims parsed on the client for security-sensitive operations like account creation. Send the complete, raw responseJsonString and the original nonce to the app's server for full verification.

  • Nonce Integrity : Generate a unique, cryptographically secure nonce for every request and never reuse a nonce across multiple requests to prevent replay attacks.

  • Cryptographic Checks : The app's server must validate the issuer (iss) field, the SD-JWT signature, and the presenter identity using the cnf field.

Files

references/android/identity/passkeys/create-passkeys.md

Before your users can authenticate with passkeys, your app must first register or create the passkey for their account.

To create the passkey, obtain details required to create the passkey from your app server, and then call the Credential Manager API, which returns a public and private key pair. The returned private key is stored in a credential provider, such as Google Password Manager, as a passkey. The public key is stored on your app server.

[!TIP] Tip: While designing authentication flows with passkeys, make sure that you follow the UX guidelines for passkeys.

Image: Passkeys are stored in a credential provider and public keys are stored on the app server **Figure 1:**Creation of passkeys

Prerequisites

Make sure that you have set up Digital Asset Links and that you target devices running Android 9 (API level 28) or higher.

Overview

This guide focuses on the changes required in your relying party client app to create a passkey, and gives a brief overview of the relying party app server implementation. To learn more about the server-side integration, see Server-side passkey registration.

  1. Add dependencies to your app: Add the required Credential Manager libraries.

  2. Instantiate Credential Manager: Create a Credential Manager instance.

  3. Get credential creation options from the app server: From your app server, send the client app the details required to create the passkey, such as information about the app, the user, as well as a challenge and other fields.

  4. Request a passkey: In your app, use the details received from the app server to create a GetPublicKeyCredentialOption object and use this object to invoke the credentialManager.getCredential() method to create a passkey.

  5. Handle the passkey creation response: When you receive the credentials on your client app, you must encode, serialize and then send the public key to the app server. You must also handle each of the exceptions that can occur in case of passkey creation.

  6. Verify and save the public key on the server: Complete the server-side steps to verify the origin of the credential and then save the public key.

  7. Notify the user: Notify the user that their passkey has been created.

[!TIP] Tip: To enhance the user experience during sign up, add functionality to restore credentials on a new device to let users seamlessly set up their existing accounts on new Android devices, and suppressing autofill dialogs on credential fields.

Add dependencies to your app

Add the following dependencies to your app module's build.gradle file:

Kotlin
dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha03")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")
}
Groovy
dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha03"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha03"
}

[!NOTE] Note: Use the latest available versions of the dependencies. Versions of androidx.credentials earlier than version 1.2 throw an UnsupportedOperationException("Post-U not supported yet") exception on Android 14.

Instantiate Credential Manager

Use your app or activity context to create a CredentialManager object.

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

Get credential creation options from your app server

When the user clicks a "Create Passkey" button or when a new user signs up, make a request from your app to your app server to obtain the information required to start the passkey registration process.

Use a FIDO-compliant library in your app server to send your client app the information required to create a passkey, such as information about the user, the app, and additional configuration properties. To learn more, see Server side passkey registration.

In the client app, decode the public key creation options sent by the app server. These are usually represented in JSON format. To learn more about how this decoding is done for web clients, see Encoding and Decoding. For Android client apps, you must handle the decoding separately.

[!NOTE] Note: On your app server, securely store the challenge so that you can later verify the origin of the credential.

The following snippet shows the structure the public key creation options sent by the app server:

{
  "challenge": "<base64url-encoded challenge>",
  "rp": {
    "name": "<relying party name>",
    "id": "<relying party host name>"
  },
  "user": {
    "id": "<base64url-encoded user ID>",
    "name": "<user name>",
    "displayName": "<user display name>"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }
  ],
  "attestation": "none",
  "excludeCredentials": [
    {
        "id": "<base64url-encoded credential ID to exclude>", 
        "type": "public-key"
    }
  ],
  "authenticatorSelection": {
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

Key fields in the public key creation options include:

  • challenge: A server-generated random string that is used to prevent replay attacks.

  • rp: Details about the app.

    • rp.name: The app's name.

    • rp.id: The app's domain or subdomain.

  • user: Details about the user.

    • id: The user's unique ID. This value must not include personally identifying information, for example, email addresses or usernames. You can use a random, 16-byte value.

    • name: A unique identifier for the account that the user will recognise, such as their email address or username. This will be displayed in the account selector. If using a username, use the same value as in password authentication.

    • displayName: An optional, user-friendly name for the account intended for display in the account selector.

  • authenticatorSelection: Details about the device that will be used for authentication.

    • authenticatorAttachment: Indicates the preferred authenticator. The possible values are as follows:

      • platform: This value is used for an authenticator built into the user's device, such as a fingerprint sensor.

      • cross-platform: This value is used for roaming devices such as security keys. It is not typically used in the passkey context.

      • Unspecified (recommended): Leaving this value unspecified provides users with the flexibility to create passkeys on their preferred devices. In most cases, leaving the parameter unspecified is the best option.

    • requireResidentKey: To create a passkey, set the value of this Boolean field to true.

    • residentKey: To create a passkey, set the value to required.

    • userVerification: Used to specify the requirements for user verification during a passkey registration. The possible values are as follows:

      • preferred: Use this value if you prioritize user experience over protection, such as in environments where user verification causes more friction than protection.

      • required: Use this value if invoking a user verification method available on the device is required.

      • discouraged: Use this value if using a user verification method is discouraged.
        To learn more about userVerification, see userVerification deep dive.

  • excludeCredentials: List credential IDs in an array to prevent the creation of a duplicate passkey if one already exists with the same credential provider.

Create a passkey

After you have parsed the server-side public key creation options, create a passkey by wrapping these options in a CreatePublicKeyCredentialRequest object and calling createCredential().

The createPublicKeyCredentialRequest includes the following:

  • requestJson: The credential creation options sent by the app server.

  • preferImmediatelyAvailableCredentials: This is an optional Boolean field that defines whether to only use locally-available or credential provider-synced credentials to fulfill the request, instead of credentials from security keys or hybrid key flows. The possible usages are as follows:

    • false (default): Use this value if the call to Credential Manager was triggered by an explicit user action.

    • true: Use this value if Credential Manager is opportunistically called, such as when first opening the app.
      If you set the value to true and there are no immediately available credentials, Credential Manager won't show any UI and the request will fail immediately, returning NoCredentialException for get requests and CreateCredentialNoCreateOptionException for create requests.

  • origin: This field is automatically set for Android apps. For browsers and similarly privileged apps that need to set origin, see Make Credential Manager calls on behalf of other parties for privileged apps.

  • isConditional: This is an optional field that defaults to false. For more information, see Automatically create a passkey.

Calling the createCredential() function launches Credential Manager's built-in bottom sheet UI that prompts the user to use a passkey and to select a credential provider and account for storage. However, if isConditional is set to true, the bottom sheet UI does not display, and the passkey is automatically created.

Automatically create a passkey

You can automatically create a passkey for a user after a successful password login by setting the isConditional parameter to true in your CreatePublicKeyCredentialRequest while creating a passkey. If the user doesn't already have a passkey, your app will automatically attempt to create one in the background and store it in the user's credential provider, such as Google Password Manager. For an example of how this is implemented, see the public sample. Image: An example of the notification Google Password Manager shows after passkey creation **Figure 2:**Google Password Manager notification

[!NOTE] Note: If a passkey is created automatically, credential providers are responsible for notifying users about a newly created passkey. Google Password Manager notifies users when a passkey is automatically created. However, other credential providers might have their own conditions and notifications for this feature.

Handle the response

After the user is verified using the device's screen lock, a passkey is created and stored in the user's selected credential provider.

The response after you successfully call createCredential() is a PublicKeyCredential object.

The PublicKeyCredential looks as follows:

{
  "id": "<identifier>",
  "type": "public-key",
  "rawId": "<identifier>",
  "response": {
    "clientDataJSON": "<ArrayBuffer encoded object with the origin and signed challenge>",
    "attestationObject": "<ArrayBuffer encoded object with the public key and other information.>"
  },
  "authenticatorAttachment": "platform"
}

In the client app, serialize the object and send it to the app server.

Add code to handle failures as shown in the following snippet:

fun handleFailure(e: CreateCredentialException) {
    when (e) {
        is CreatePublicKeyCredentialDomException -> {
            // Handle the passkey DOM errors thrown according to the
            // WebAuthn spec.
        }
        is CreateCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to register the credential.
        }
        is CreateCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is CreateCredentialProviderConfigurationException -> {
            // Your app is missing the provider configuration dependency.
            // Most likely, you're missing the
            // "credentials-play-services-auth" module.
        }
        is CreateCredentialCustomException -> {
            // You have encountered an error from a 3rd-party SDK. If you
            // make the API call with a request object that's a subclass of
            // CreateCustomCredentialRequest using a 3rd-party SDK, then you
            // should check for any custom exception type constants within
            // that SDK to match with e.type. Otherwise, drop or log the
            // exception.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}")
    }
}

Verify and save the public key on the app server

On the app server, you must verify the public key credential and then save the public key.

To verify the public key credential's origin, compare it against an allow list of approved apps. If a key has an unrecognized origin, reject it.

[!NOTE] Note: An app's origin is based on its unique identity, which is the SHA-256 fingerprint of its signing certificate.

To obtain the app's SHA 256 fingerprint:

  1. Print your release app's signing certificate by running the following command in a terminal:

    keytool -list -keystore <path-to-apk-signing-keystore>

    In the response, identify the signing certificate's SHA 256 fingerprint, mentioned as Certificate fingerprints block : SHA256.

  2. Encode the SHA256 fingerprint with base64url encoding. This Python example demonstrates how to properly encode the fingerprint:

    import binascii
    import base64
    fingerprint = '<SHA256 finerprint>' # your app's SHA256 fingerprint
    print(base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))
    
  3. Append android:apk-key-hash: to the start of the output from the previous step so that you get something that is similar to the following:

    android:apk-key-hash:<encoded SHA 256 fingerprint>

    The result should match with an allowed origin on your app server. If you have multiple signing certificates, such as certificates for debugging and release, or multiple apps, then repeat the process and accept all the origins as valid on the app server.

[!NOTE] Note: When you save the passkey on the app server, make sure that you save the Authenticator Attestation Globally Unique Identifier (AAGUID) from the client data. The AAGUID is a unique number that identifies the model of the authenticator. For more information, see Manage passkeys.

Notify the user

After the passkey is successfully created, notify your users about the passkey and inform them that they can manage their passkeys from their credential provider app or from within the app settings. Notify users by using a custom dialog, notification, or snackbar. Since an unexpected passkey creation by a malicious entity requires an immediate security alert, consider supplementing these in-app methods with external communication, such as an email.

Enhance the user experience

To enhance the user experience while implementing sign up with Credential Manager, consider adding functionality for restore credentials and suppress autofill dialogs.

Add functionality to restore credentials on a new device

To allow users to seamlessly log into their accounts on a new device, implement the Restore Credentials functionality. Adding restore credentials with BackupAgent logs users in when they open your restored app on a new device, letting them use your app right away.

Suppress autofill on credential fields (optional)

For app screens where users are expected to use Credential Manager's bottom sheet UI for authentication, add the isCredential attribute to the username and password fields. This suppresses autofill dialogs (FillDialog and SaveDialog) from overlapping with Credential Manager's bottom sheet UI.

The isCredential attribute is supported on Android 14 and higher.

The following example demonstrates how you can add the isCredential attribute to the relevant username and password fields in the relevant views for your app:

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
android:isCredential="true" /\>

Next steps

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Google LLC
Publisher
Android (Google) (Official)
Distributed by
Android (Google)
Source
android/skills · identity/verified-email · commit bf68e2d
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE.txt)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Google LLC or Android (Google).

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download verified-email.zip