Fazer login do usuário com o Gerenciador de credenciais

O Gerenciador de credenciais é uma nova API do Jetpack que oferece suporte a login múltiplo, como nome de usuário e senha, chaves de acesso e soluções de login federadas (como o Fazer login com o Google) em uma única API, simplificando a integração para desenvolvedores.

Além disso, para os usuários, o Gerenciador de credenciais unifica a interface de login em todos os métodos de autenticação, deixando o processo de login em apps mais claro e fácil, independente do método escolhido.

Nesta página, explicamos o conceito de chaves de acesso e as etapas para implementar o suporte do lado do cliente em soluções de autenticação, incluindo chaves de acesso, usando a API Credential Manager. Há também uma página de perguntas frequentes separada que responde perguntas mais detalhadas e específicas.

Seu feedback é essencial para melhorar a API Credential Manager. Compartilhe qualquer problema encontrado ou ideia para melhorar a API usando o link abaixo:

Enviar feedback

Sobre chaves de acesso

As chaves de acesso são substitutas mais simples e seguras para as senhas. Com as chaves de acesso, os usuários podem fazer login em apps e sites usando um sensor biométrico (como reconhecimento facial ou de impressão digital), um PIN ou um padrão. Assim, o usuário tem uma experiência de login simplificada, sem precisar se lembrar de nomes de usuário ou senhas definidas.

As chaves de acesso usam WebAuthn (Web Authentication), um padrão desenvolvido em parceria pela FIDO Alliance e o World Wide Web Consortium (W3C). WebAuthn usa a criptografia de chave pública para autenticar o usuário. O site ou app em que o usuário faz login pode conferir e armazenar a chave pública, mas nunca a chave privada. A chave privada fica oculta e é mantida em segurança. Por serem exclusivas e ficarem vinculadas ao site ou app, as chaves de acesso não correm risco de phishing, o que aumenta ainda mais a segurança.

O Gerenciador de credenciais permite que os usuários criem chaves de acesso e armazenem essas informações no Gerenciador de senhas do Google.

Pré-requisitos

Para usar o Gerenciador de credenciais, siga as etapas desta seção.

Usar uma versão recente da plataforma

O Gerenciador de credenciais pode ser usado no Android 4.4 (nível 19 da API) e versões mais recentes.

Adicionar dependências ao app

Adicione estas dependências ao script de build do módulo do app:

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.3.0-alpha01")

    // optional - needed for credentials support from play services, for devices running
    // Android 13 and below.
    implementation("androidx.credentials:credentials-play-services-auth:1.3.0-alpha01")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.3.0-alpha01"

    // optional - needed for credentials support from play services, for devices running
    // Android 13 and below.
    implementation "androidx.credentials:credentials-play-services-auth:1.3.0-alpha01"
}

Preservar classes no arquivo ProGuard

No arquivo proguard-rules.pro do módulo, adicione estas diretivas:

-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
  *;
}

Saiba mais sobre como reduzir, ofuscar e otimizar seu app.

Adicionar suporte para Digital Asset Links

Para ativar o suporte a chaves de acesso no seu app Android, associe o app a um site próprio. Para declarar essa associação, siga estas etapas:

  1. Crie um arquivo JSON do Digital Asset Links. Por exemplo, para declarar que o site https://signin.example.com e um app Android com o nome de pacote com.example podem compartilhar credenciais de login, crie um arquivo chamado assetlinks.json com o seguinte conteúdo:

    [
      {
        "relation" : [
          "delegate_permission/common.handle_all_urls",
          "delegate_permission/common.get_login_creds"
        ],
        "target" : {
          "namespace" : "android_app",
          "package_name" : "com.example.android",
          "sha256_cert_fingerprints" : [
            SHA_HEX_VALUE
          ]
        }
      }
    ]
    

    O campo relation é uma matriz de uma ou mais strings que descrevem o relacionamento que está sendo declarado. Para declarar que apps e sites compartilham credenciais de login, especifique as relações como delegate_permission/handle_all_urls e delegate_permission/common.get_login_creds.

    O campo target é um objeto que especifica o recurso a que a declaração se aplica. Os campos a seguir identificam um site:

    namespace web
    site

    O URL do site, no formato https://domain[:optional_port], por exemplo, https://www.example.com.

    O domain precisa ser totalmente qualificado, e optional_port precisa ser omitido ao usar a porta 443 para HTTPS.

    Um destino site só pode ser um domínio raiz. Não é possível limitar uma associação de app a um subdiretório específico. Não inclua um caminho no URL, por exemplo, uma barra.

    Não é esperado que os subdomínios apresentem correspondências: ou seja, se você especificar domain como www.example.com, o domínio www.counter.example.com não será associado ao app.

    Os campos a seguir identificam um app Android.

    namespace android_app
    package_name Nome do pacote declarado no manifesto do app. Por exemplo, com.example.android
    sha256_cert_fingerprints As impressões digitais SHA256 do certificado de assinatura do app.
  2. Hospede o arquivo JSON do Digital Assets Link no seguinte local no domínio de login:

    https://domain[:optional_port]/.well-known/assetlinks.json
    

    Por exemplo, se o domínio de login for signin.example.com, hospede o arquivo JSON em https://signin.example.com/.well-known/assetlinks.json.

    O tipo MIME do arquivo Digital Assets Link precisa ser JSON. Verifique se o servidor envia um cabeçalho Content-Type: application/json na resposta.

  3. Verifique se o host permite que o Google recupere o arquivo do Digital Asset Link. Se você tiver um arquivo robots.txt, ele precisa permitir que o agente do Googlebot extraia /.well-known/assetlinks.json. A maioria dos sites pode permitir que qualquer agente automatizado extraia arquivos no caminho /.well-known/ para que outros serviços possam acessar os metadados nesses arquivos:

    User-agent: *
    Allow: /.well-known/
    
  4. Adicione a seguinte linha ao arquivo de manifesto em <application>:

    <meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
    
  5. Se você está usando o login com senha pelo Gerenciador de credenciais, siga esta etapa para configurar a vinculação de ativos digitais no manifesto. Esta etapa não será necessária se você estiver usando apenas chaves de acesso.

    Declare a associação no app Android. Adicione um objeto que especifique os arquivos assetlinks.json a serem carregados. É necessário fazer o escape de todos os apóstrofos e aspas que você usa na string. Por exemplo:

    <string name="asset_statements" translatable="false">
    [{
      \"include\": \"https://signin.example.com/.well-known/assetlinks.json\"
    }]
    </string>
    
    > GET /.well-known/assetlinks.json HTTP/1.1
    > User-Agent: curl/7.35.0
    > Host: signin.example.com
    
    < HTTP/1.1 200 OK
    < Content-Type: application/json
    

Configurar o Gerenciador de credenciais

Para configurar e inicializar um objeto CredentialManager, adicione uma lógica parecida com esta:

Kotlin

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

Java

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

Indicar campos de credenciais

No Android 14 e versões mais recentes, o atributo isCredential pode ser usado para indicar campos de credenciais, como nome de usuário ou senha. Esse atributo indica que essa visualização é um campo de credencial criado para funcionar com o Gerenciador de credenciais e provedores de credenciais de terceiros, ajudando os serviços de preenchimento automático a fornecer melhores sugestões. Quando o app usa a API Credential Manager, a página inferior do Gerenciador de credenciais com credenciais disponíveis é mostrada e não é necessário mostrar a caixa de diálogo do preenchimento automático para nome de usuário ou senha.

Para usar o atributo isCredential, adicione-o às visualizações relevantes:

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

Fazer login do usuário

Para extrair todas as opções de chave de acesso e senha associadas à conta do usuário, siga estas etapas:

  1. Inicialize as opções de autenticação de senha e chave de acesso:

    Kotlin

    // Retrieves the user's saved password for your app from their
    // password provider.
    val getPasswordOption = GetPasswordOption()
    
    // Get passkey from the user's public key credential provider.
    val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(
        requestJson = requestJson
    )

    Java

    // Retrieves the user's saved password for your app from their
    // password provider.
    GetPasswordOption getPasswordOption = new GetPasswordOption();
    
    // Get passkey from the user's public key credential provider.
    GetPublicKeyCredentialOption getPublicKeyCredentialOption =
            new GetPublicKeyCredentialOption(requestJson);
  2. Use as opções extraídas da etapa anterior para criar a solicitação de login.

    Kotlin

    val getCredRequest = GetCredentialRequest(
        listOf(getPasswordOption, getPublicKeyCredentialOption)
    )

    Java

    GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder()
        .addCredentialOption(getPasswordOption)
        .addCredentialOption(getPublicKeyCredentialOption)
        .build();
  3. Inicie o fluxo de login:

    Kotlin

    coroutineScope.launch {
        try {
            val result = credentialManager.getCredential(
                // Use an activity-based context to avoid undefined system UI
                // launching behavior.
                context = activityContext,
                request = getCredRequest
            )
            handleSignIn(result)
        } catch (e : GetCredentialException) {
            handleFailure(e)
        }
    }
    
    fun handleSignIn(result: GetCredentialResponse) {
        // Handle the successfully returned credential.
        val credential = result.credential
    
        when (credential) {
            is PublicKeyCredential -> {
                val responseJson = credential.authenticationResponseJson
                // Share responseJson i.e. a GetCredentialResponse on your server to
                // validate and  authenticate
            }
            is PasswordCredential -> {
                val username = credential.id
                val password = credential.password
                // Use id and password to send to your server to validate
                // and authenticate
            }
          is CustomCredential -> {
              // If you are also using any external sign-in libraries, parse them
              // here with the utility functions provided.
              if (credential.type == ExampleCustomCredential.TYPE)  {
              try {
                  val ExampleCustomCredential = ExampleCustomCredential.createFrom(credential.data)
                  // Extract the required credentials and complete the authentication as per
                  // the federated sign in or any external sign in library flow
                  } catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) {
                      // Unlikely to happen. If it does, you likely need to update the dependency
                      // version of your external sign-in library.
                      Log.e(TAG, "Failed to parse an ExampleCustomCredential", e)
                  }
              } else {
                // Catch any unrecognized custom credential type here.
                Log.e(TAG, "Unexpected type of credential")
              }
            } else -> {
                // Catch any unrecognized credential type here.
                Log.e(TAG, "Unexpected type of credential")
            }
        }
    }

    Java

    credentialManager.getCredentialAsync(
        // Use activity based context to avoid undefined
        // system UI launching behavior
        activity,
        getCredRequest,
        cancellationSignal,
        <executor>,
        new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
            @Override
            public void onResult(GetCredentialResponse result) {
                handleSignIn(result);
            }
    
            @Override
            public void onError(GetCredentialException e) {
                handleFailure(e);
            }
        }
    );
    
    public void handleSignIn(GetCredentialResponse result) {
        // Handle the successfully returned credential.
        Credential credential = result.getCredential();
        if (credential instanceof PublicKeyCredential) {
            String responseJson = ((PublicKeyCredential) credential).getAuthenticationResponseJson();
            // Share responseJson i.e. a GetCredentialResponse on your server to validate and authenticate
        } else if (credential instanceof PasswordCredential) {
            String username = ((PasswordCredential) credential).getId();
            String password = ((PasswordCredential) credential).getPassword();
            // Use id and password to send to your server to validate and authenticate
        } else if (credential instanceof CustomCredential) {
            if (ExampleCustomCredential.TYPE.equals(credential.getType())) {
                try {
                    ExampleCustomCredential customCred = ExampleCustomCredential.createFrom(customCredential.getData());
                    // Extract the required credentials and complete the
                    // authentication as per the federated sign in or any external
                    // sign in library flow
                } catch (ExampleCustomCredential.ExampleCustomCredentialParsingException e) {
                    // Unlikely to happen. If it does, you likely need to update the
                    // dependency version of your external sign-in library.
                    Log.e(TAG, "Failed to parse an ExampleCustomCredential", e);
                }
            } else {
                // Catch any unrecognized custom credential type here.
                Log.e(TAG, "Unexpected type of credential");
            }
        } else {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential");
        }
    }

O exemplo abaixo mostra como formatar a solicitação JSON ao receber uma chave de acesso:

{
  "challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
  "allowCredentials": [],
  "timeout": 1800000,
  "userVerification": "required",
  "rpId": "credential-manager-app-test.glitch.me"
}

O exemplo abaixo mostra como uma resposta JSON pode ficar depois que você receber uma credencial de chave pública:

{
  "id": "KEDetxZcUfinhVi6Za5nZQ",
  "type": "public-key",
  "rawId": "KEDetxZcUfinhVi6Za5nZQ",
  "response": {
    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
    "authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
    "signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
    "userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
  }
}

Processar exceções quando não há credenciais disponíveis

Em alguns casos, o usuário pode não ter nenhuma credencial disponível ou não permitir o uso de uma credencial disponível. Se getCredential() for invocado e nenhuma credencial for encontrada, uma NoCredentialException será retornada. Se isso acontecer, seu código vai processar as instâncias NoCredentialException.

Kotlin

try {
  val credential = credentialManager.getCredential(credentialRequest)
} catch (e: NoCredentialException) {
  Log.e("CredentialManager", "No credential available", e)
}

Java

try {
  Credential credential = credentialManager.getCredential(credentialRequest);
} catch (NoCredentialException e) {
  Log.e("CredentialManager", "No credential available", e);
}

No Android 14 ou em versões mais recentes, é possível reduzir a latência ao mostrar o seletor de contas usando o método prepareGetCredential() antes de chamar getCredential().

Kotlin

val response = credentialManager.prepareGetCredential(
  GetCredentialRequest(
    listOf(
      <getPublicKeyCredentialOption>,
      <getPasswordOption>
    )
  )
}

Java

GetCredentialResponse response = credentialManager.prepareGetCredential(
  new GetCredentialRequest(
    Arrays.asList(
      new PublicKeyCredentialOption(),
      new PasswordOption()
    )
  )
);

O método prepareGetCredential() não invoca elementos da interface. Isso só ajuda você a realizar o trabalho de preparação para poder iniciar mais tarde a operação get-credential restante (que envolve interfaces) usando a API getCredential().

Os dados em cache são retornados em um objeto PrepareGetCredentialResponse. Se houver credenciais, os resultados serão armazenados em cache e você poderá iniciar mais tarde a API getCredential() restante para mostrar o seletor de contas com os dados armazenados em cache.

Fluxos de registro

Você pode registrar um usuário para usar a autenticação com uma chave de acesso ou senha.

Criar uma chave de acesso

Para oferecer aos usuários a opção de criar uma chave de acesso e usá-la na reautenticação, registre uma credencial de usuário usando um objeto CreatePublicKeyCredentialRequest.

Kotlin

fun createPasskey(requestJson: String, preferImmediatelyAvailableCredentials: Boolean) {
    val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest(
        // Contains the request in JSON format. Uses the standard WebAuthn
        // web JSON spec.
        requestJson = requestJson,
        // Defines whether you prefer to use only immediately available
        // credentials, not hybrid credentials, to fulfill this request.
        // This value is false by default.
        preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
    )

    // Execute CreateCredentialRequest asynchronously to register credentials
    // for a user account. Handle success and failure cases with the result and
    // exceptions, respectively.
    coroutineScope.launch {
        try {
            val result = credentialManager.createCredential(
                // Use an activity-based context to avoid undefined system
                // UI launching behavior
                context = activityContext,
                request = createPublicKeyCredentialRequest,
            )
            handlePasskeyRegistrationResult(result)
        } catch (e : CreateCredentialException){
            handleFailure(e)
        }
    }
}

fun handleFailure(e: CreateCredentialException) {
    when (e) {
        is CreatePublicKeyCredentialDomException -> {
            // Handle the passkey DOM errors thrown according to the
            // WebAuthn spec.
            handlePasskeyError(e.domError)
        }
        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 CreateCredentialUnknownException -> ...
        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}")
    }
}

Java

public void createPasskey(String requestJson, boolean preferImmediatelyAvailableCredentials) {
    CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest =
            // `requestJson` contains the request in JSON format. Uses the standard
            // WebAuthn web JSON spec.
            // `preferImmediatelyAvailableCredentials` defines whether you prefer
            // to only use immediately available credentials, not  hybrid credentials,
            // to fulfill this request. This value is false by default.
            new CreatePublicKeyCredentialRequest(
                requestJson, preferImmediatelyAvailableCredentials);

    // Execute CreateCredentialRequest asynchronously to register credentials
    // for a user account. Handle success and failure cases with the result and
    // exceptions, respectively.
    credentialManager.createCredentialAsync(
        // Use an activity-based context to avoid undefined system
        // UI launching behavior
        requireActivity(),
        createPublicKeyCredentialRequest,
        cancellationSignal,
        executor,
        new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
            @Override
            public void onResult(CreateCredentialResponse result) {
                handleSuccessfulCreatePasskeyResult(result);
            }

            @Override
            public void onError(CreateCredentialException e) {
                if (e instanceof CreatePublicKeyCredentialDomException) {
                    // Handle the passkey DOM errors thrown according to the
                    // WebAuthn spec.
                    handlePasskeyError(((CreatePublicKeyCredentialDomException)e).getDomError());
                } else if (e instanceof CreateCredentialCancellationException) {
                    // The user intentionally canceled the operation and chose not
                    // to register the credential.
                } else if (e instanceof CreateCredentialInterruptedException) {
                    // Retry-able error. Consider retrying the call.
                } else if (e instanceof CreateCredentialProviderConfigurationException) {
                    // Your app is missing the provider configuration dependency.
                    // Most likely, you're missing the
                    // "credentials-play-services-auth" module.
                } else if (e instanceof CreateCredentialUnknownException) {
                } else if (e instanceof 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.getClass().getName());
                }
            }
        }
    );
}

Formatar a solicitação JSON

Depois de criar uma chave de acesso, associe-a à conta de um usuário e armazene-a no servidor. O exemplo de código abaixo mostra como formatar a solicitação JSON ao criar uma chave de acesso.

Esta postagem do blog sobre como oferecer a autenticação integrada nos seus apps (link em inglês) mostra como formatar sua solicitação JSON ao criar chaves de acesso e ao autenticar usando essas chaves. Também explica por que as senhas não são uma solução de autenticação eficaz, como aproveitar as credenciais biométricas existentes, como associar seu app a um site seu, como criar chaves de acesso e como autenticar usando chaves de acesso.

{
  "challenge": "abc123",
  "rp": {
    "name": "Credential Manager example",
    "id": "credential-manager-test.example.com"
  },
  "user": {
    "id": "def456",
    "name": "helloandroid@gmail.com",
    "displayName": "helloandroid@gmail.com"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    },
    {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [
    {"id": "ghi789", "type": "public-key"},
    {"id": "jkl012", "type": "public-key"}
  ],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

Definir valores para authenticatorAttachment

O parâmetro authenticatorAttachment só pode ser definido no momento da criação da credencial. É possível especificar platform, cross-platform ou nenhum valor. Na maioria dos casos, nenhum valor é recomendado.

  • platform: para registrar o dispositivo atual do usuário ou solicitar que o usuário da senha faça upgrade para chaves de acesso após o login, defina authenticatorAttachment como platform.
  • cross-platform: esse valor costuma ser usado ao registrar credenciais de vários fatores e não é usado em um contexto de chave de acesso.
  • Nenhum valor: para oferecer aos usuários a flexibilidade de criar chaves de acesso nos dispositivos preferidos deles (como nas configurações da conta), o parâmetro authenticatorAttachment não pode ser especificado quando o usuário decide adicionar uma chave de acesso. Na maioria dos casos, deixar o parâmetro não especificado é a melhor opção.

Impedir a criação de chaves de acesso duplicadas

Liste os IDs de credenciais na matriz opcional excludeCredentials para evitar a criação de uma nova chave de acesso, caso já exista uma com o mesmo provedor.

Processar a resposta JSON

O snippet de código abaixo mostra um exemplo de resposta JSON para criar uma credencial de chave pública. Saiba mais sobre como processar a credencial de chave pública retornada (link em inglês).

{
  "id": "KEDetxZcUfinhVi6Za5nZQ",
  "type": "public-key",
  "rawId": "KEDetxZcUfinhVi6Za5nZQ",
  "response": {
    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
    "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
  }
}

Verificar a origem dos dados do cliente JSON

A origin (link em inglês) representa o aplicativo ou site de origem de uma solicitação e é usada pelas chaves de acesso como proteção contra ataques de phishing. O servidor do app precisa verificar a origem dos dados do cliente em uma lista de permissões de apps e sites aprovados. Se o servidor receber uma solicitação de um app ou site de origem não reconhecida, ela será rejeitada.

No caso da Web, origin reflete a mesma origem do site (link em inglês) em que a credencial foi conectada. Por exemplo, considerando um URL de https://www.example.com:8443/store?category=shoes#athletic, a origin é https://www.example.com:8443.

Em apps Android, o user agent define a origin automaticamente como a assinatura do app de chamada. Essa assinatura precisa ser verificada como uma correspondência no seu servidor para validar o autor da chamada da API da chave de acesso. A origin do Android é um URI derivado do hash SHA-256 do certificado de assinatura do APK, como:

android:apk-key-hash:<sha256_hash-of-apk-signing-cert>

Para encontrar os hashes SHA-256 dos certificados de assinatura de um keystore, execute o seguinte comando do terminal:

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

Os hashes SHA-256 estão em um formato hexadecimal delimitado por dois-pontos (91:F7:CB:F9:D6:81…), e os valores de origin do Android são codificados em base64url. Este exemplo em Python demonstra como converter o formato de hash em um formato hexadecimal compatível separado por dois-pontos:

import binascii
import base64
fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5'
print("android:apk-key-hash:" + base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))

Substitua o valor de fingerprint pelo seu próprio valor. Confira um exemplo de resultado:

android:apk-key-hash:kffL-daBUxvHpY-4M8yhTavt5QnFEI2LsexohxrGPYU

Você pode fazer a correspondência dessa string como uma origem permitida no seu servidor. Se você tiver vários certificados de assinatura, como certificados para depuração e lançamento, ou vários apps, repita o processo e aceite todas essas origens como válidas no servidor.

Salvar a senha de um usuário

Se o usuário informar um nome de usuário e uma senha durante o fluxo de autenticação do app, você poderá registrar uma credencial para autenticá-lo. Para isso, crie um objeto CreatePasswordRequest:

Kotlin

fun registerPassword(username: String, password: String) {
    // Initialize a CreatePasswordRequest object.
    val createPasswordRequest =
            CreatePasswordRequest(id = username, password = password)

    // Create credential and handle result.
    coroutineScope.launch {
        try {
            val result =
                credentialManager.createCredential(
                    // Use an activity based context to avoid undefined
                    // system UI launching behavior.
                    activityContext,
                    createPasswordRequest
                  )
            handleRegisterPasswordResult(result)
        } catch (e: CreateCredentialException) {
            handleFailure(e)
        }
    }
}

Java

void registerPassword(String username, String password) {
    // Initialize a CreatePasswordRequest object.
    CreatePasswordRequest createPasswordRequest =
        new CreatePasswordRequest(username, password);

    // Register the username and password.
    credentialManager.createCredentialAsync(
        // Use an activity-based context to avoid undefined
        // system UI launching behavior
        requireActivity(),
        createPasswordRequest,
        cancellationSignal,
        executor,
        new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
            @Override
            public void onResult(CreateCredentialResponse result) {
                handleResult(result);
            }

            @Override
            public void onError(CreateCredentialException e) {
                handleFailure(e);
            }
        }
    );
}

Suporte à recuperação de credenciais

Se um usuário não tiver mais acesso a um dispositivo em que armazenou as credenciais, talvez precise recuperá-las de um backup on-line seguro. Para saber mais sobre como oferecer suporte para esse processo de recuperação de credenciais, leia a seção sobre "Como recuperar o acesso ou adicionar novos dispositivos" na postagem do blog Segurança de chaves de acesso no Gerenciador de senhas do Google (em inglês).

Adicionar suporte a ferramentas de gerenciamento de senhas com o URL conhecido dos endpoints das chaves de acesso

Para integração total e compatibilidade futura com ferramentas de gerenciamento de senhas e credenciais, recomendamos adicionar suporte a URLs conhecidos de endpoints de chave de acesso. Esse é um protocolo aberto para que as partes alinhadas anunciem formalmente o suporte a chaves de acesso e forneçam links diretos para a inscrição e o gerenciamento de chaves de acesso.

  1. Para uma parte confiável em https://example.com, que tem um site com apps Android e iOS, o URL conhecido seria https://example.com/.well-known/passkey-endpoints.
  2. Quando o URL é consultado, a resposta precisa usar este esquema:

    {
      "enroll": "https://example.com/account/manage/passkeys/create"
      "manage": "https://example.com/account/manage/passkeys"
    }
    
  3. Para que esse link seja aberto diretamente no seu app, e não na Web, use Links do app Android.

  4. Confira mais detalhes sobre isso em URLs conhecidos com endpoints de chave de acesso no GitHub (link em inglês).

Resolver erros comuns

A tabela abaixo apresenta códigos e descrições de erros comuns, além de fornecer algumas informações sobre as causas:

Código do erro e descrição Causa
On Begin Sign In Failure: 16: o autor da chamada foi bloqueado temporariamente porque muitas solicitações de login foram canceladas.

Se encontrar esse período de bloqueio de 24 horas durante o desenvolvimento, você pode limpar o armazenamento de apps do Google Play Services para redefini-lo.

Como alternativa, para ativar esse período de bloqueio em um dispositivo de teste ou emulador, abra o app Telefone e insira este código: *#*#66382723#*#*. O app Telefone vai limpar todas as entradas e pode até fechar, mas nenhuma mensagem de confirmação vai aparecer.

On Begin Sign In Failure: 8: erro interno desconhecido.
  1. A Conta do Google não está configurada corretamente no dispositivo.
  2. O JSON da chave de acesso está sendo criado incorretamente.
CreatePublicKeyCredentialDomException: a solicitação recebida não pode ser validada. O ID do pacote do app não está registrado no seu servidor. Corrija isso na integração do lado do servidor.
CreateCredentialUnknownException: durante o salvamento de senha, foi encontrada uma resposta de falha de senha de um toque 16: ignorando o salvamento de senhas, já que o usuário provavelmente receberá uma solicitação de preenchimento automático do Android. Esse erro ocorre apenas no Android 13 e versões anteriores e apenas se o Google for o provedor de preenchimento automático. Nesse caso, o preenchimento automático mostra uma solicitação de salvamento, e a senha é salva no Gerenciador de senhas do Google. As credenciais salvas usando o Preenchimento automático do Google são compartilhadas bidirecionalmente com a API Credential Manager. Como resultado, esse erro pode ser ignorado com segurança.

Outros recursos

Para saber mais sobre a API Credential Manager e as chaves de acesso, consulte estes recursos: