Compatibilidade de versões
A restauração de credenciais do Gerenciador de credenciais funciona em dispositivos com o Android 9 (nível 28 da API)
e versões mais recentes, o Google Play Services (GMS) versão principal 242200000
(v24.22) ou mais recente e a versão 1.5.0 ou mais recente da biblioteca
androidx.credentials. Em dispositivos sem o Google Play Services ou com uma versão mais antiga do GMS,
capture CreateCredentialProviderConfigurationException e
GetCredentialProviderConfigurationException para que o app volte normalmente
ao login padrão.
Pré-requisitos
Configure um servidor de parte confiável da WebAuthn semelhante ao servidor para chaves de acesso. Se você já tiver um servidor configurado para processar a autenticação com chaves de acesso, poderá reutilizar os mesmos endpoints de registro e declaração da WebAuthn para as chaves de restauração.
Dependências
Adicione as seguintes dependências ao arquivo build.gradle do módulo do app:
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.7.0-alpha02") implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.7.0-alpha02" implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02" }
A Restore Credentials está disponível na versão 1.5.0 e mais recentes da
biblioteca androidx.credentials. No entanto, recomendamos usar as versões estáveis
mais recentes das dependências sempre que possível.
Como o android:allowBackup afeta o Restore Credentials
O atributo de manifesto android:allowBackup controla o backup de dados do app,
que interage com a API Restore Credentials de três maneiras distintas:
- Transferência de chave de restauração (independente de
allowBackup): o Google Play Services transfere o par de chavesRestoreCredentialusando backup na nuvem criptografado de ponta a ponta ou transferência local entre dispositivos (D2D, na sigla em inglês), independentemente deandroid:allowBackupsertrueoufalse. Para transferências D2D, a chave de restauração é sempre transferida. A única condição em que ela não é transferida é durante um backup na nuvem quando o usuário desativou o backup na nuvem nas configurações do Android. - Restauração em segundo plano no
BackupAgent(requerandroid:allowBackup="true"): o login silencioso em segundo plano antes que o usuário abra o app no novo dispositivo (BackupAgent.onRestoreFinished()) só é executado quandoandroid:allowBackup="true"eandroid:backupAgentestão configurados no manifesto. Se forandroid:allowBackup="false", restaure a sessão quando o usuário iniciar aActivityprincipal pela primeira vez. Também é possível implementar as duas abordagens para tornar a restauração mais robusta. - Teste de backup na nuvem do Android Studio (requer
android:allowBackup="true"): para testar o tipo de backup na nuvem na ferramenta de backup e restauração do Android Studio, é necessário usar oandroid:allowBackup="true", enquanto o teste de dispositivo para dispositivo funciona independentemente doallowBackup.
Visão geral
A API Restore Credentials segue um ciclo de vida de três etapas nos dispositivos antigos e novos do usuário:
- Criar uma chave de restauração no dispositivo anterior: depois que o usuário fizer login (ou na inicialização do app, se o usuário já estiver conectado e nenhuma chave de restauração estiver
registrada no armazenamento local), busque as opções de criação no servidor da parte confiável
e chame
credentialManager.createCredential(). - Fazer login com a chave de restauração no novo dispositivo: recupere a
RestoreCredentialtransferida usando uma estratégia de dois níveis: primeiro, silenciosamente emBackupAgent.onRestoreFinished()(quandoandroid:allowBackup="true") para que a sincronização em segundo plano e as notificações push sejam retomadas imediatamente e, em segundo lugar, como um substituto no inicializadorActivity.onCreate()na primeira inicialização do app. - Excluir a chave de restauração ao sair: quando o usuário sair ou
a sessão dele for invalidada pelo servidor, chame
credentialManager.clearCredentialState()comClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL)e limpe a flag de sincronização local.
Criar uma chave de restauração
O app precisa abranger todos os casos de login de um usuário para garantir que os usuários ativos tenham uma chave de restauração criada. Crie a chave de restauração nos seguintes cenários:
- Se o usuário estiver conectado e uma chave de restauração ainda não tiver sido criada (como no
método
onCreate()para oActivityprincipal). - Quando o usuário está fazendo login ou concluindo um fluxo de registro de nova conta.
Para evitar chamar createCredential() sempre que o app for iniciado, armazene uma flag de sincronização local, como has_synced_restore_credential, no armazenamento local. Não armazene
essa flag em SharedPreferences ou DataStore com backup padrão, a menos que esse
arquivo seja excluído em data_extraction_rules.xml. Caso contrário, o Backup automático vai copiar
has_synced_restore_credential = true para o novo dispositivo e impedir que ele
gere ou alterne a própria chave de restauração.
Instanciar o Credential Manager
Use o contexto de atividade do app para instanciar um objeto CredentialManager.
// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)
Receber opções de criação de credenciais do servidor de apps
Use uma biblioteca compatível com FIDO no servidor do app para enviar ao app cliente o
JSON PublicKeyCredentialCreationOptions necessário para criar a credencial de
restauração. Para mais informações sobre a implementação do lado do servidor, consulte
Orientação do lado do servidor.
O JSON das opções de criação segue a mesma estrutura do registro de chave de acesso:
{
"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"
}
}
Verifique se o JSON das opções de criação atende a estes requisitos:
challengeeuser.id: codifique os dois valores como strings Base64URL sem preenchimento (substituindo+por-,/por_e removendo o=final). Caracteres Base64 padrão ou IDs de números inteiros brutos causamCreateRestoreCredentialDomExceptionouIllegalArgumentException.rp.id: defina como o domínio da parte confiável (por exemplo,example.com).pubKeyCredParams: inclua{"type": "public-key", "alg": -7}(ES256) e{"type": "public-key", "alg": -257}(RS256).
Criar a chave de restauração
Depois de receber o JSON de opções de criação de chave pública do servidor, envolva o
JSON em um objeto CreateRestoreCredentialRequest e chame
createCredential() no objeto CredentialManager.
// requestJson contains the credential creation options sent by the server
val createRestoreRequest = CreateRestoreCredentialRequest(requestJson)
val response = try {
credentialManager.createCredential(context, createRestoreRequest)
} catch (e: E2eeUnavailableException) {
// Fall back to local device-to-device (D2D) restore if cloud E2EE is unavailable
credentialManager.createCredential(
context,
CreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false),
)
}
Pontos principais sobre o código
O objeto
CreateRestoreCredentialRequestcontém estes campos:requestJson: as opções de criação de credenciais enviadas pelo servidor do app no formato da API Web Authentication paraPublicKeyCredentialCreationOptionsJSON.isCloudBackupEnabled: campoBooleanpara determinar se a chave de restauração precisa ser armazenada em backup na nuvem. Por padrão, essa flag étrue. Esse campo tem os seguintes valores:true: (recomendado) esse valor permite o backup de chaves de restauração na nuvem se o usuário tiver o Backup do Google e a criptografia de ponta a ponta, como um bloqueio de tela, ativados.false: esse valor salva a chave localmente, e não na nuvem. A chave não estará disponível no novo dispositivo se o usuário optar por restaurar da nuvem, mas ainda será transferida durante uma configuração local de cabo ou Wi-Fi de dispositivo para dispositivo (D2D).
Processar a resposta de criação de credenciais
A API Credential Manager retorna uma resposta do tipo
CreateRestoreCredentialResponse. Essa resposta contém a resposta de registro da credencial de chave pública no formato JSON.
Envie response.registrationResponseJson do app para o servidor da parte confiável
para verificar e armazenar a chave pública junto com a conta do usuário e, em seguida,
registre o marcador de sincronização local no armazenamento local. Para mais informações sobre
a verificação do lado do servidor, consulte as orientações para chaves de acesso.
Durante o processo de criação da chave de restauração, processe estas exceções:
CreateRestoreCredentialDomException: essa exceção ocorre serequestJsonfor inválido e não seguir o formato WebAuthn paraPublicKeyCredentialCreationOptionsJSON.E2eeUnavailableException: essa exceção ocorre seisCloudBackupEnabledfortrue, mas o dispositivo do usuário não tiver backup de dados ou criptografia de ponta a ponta, como um bloqueio de tela.
Para garantir que as credenciais de restauração sejam criadas para transferências locais de dispositivo para dispositivo (D2D), mesmo quando a E2EE na nuvem não estiver disponível, captureE2eeUnavailableExceptione tente novamentecreateCredential()comCreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false).IllegalArgumentException: essa exceção ocorre serequestJsonestiver vazio, não for um JSON válido ou não tiver umuser.idcodificado em Base64URL válido que esteja em conformidade com as especificações do WebAuthn.
Fazer login com uma chave de restauração
Use as credenciais de restauração para fazer login silenciosamente durante o processo de configuração do dispositivo.
Receber opções de recuperação de credenciais do servidor de apps
Antes de chamar getCredential() no novo dispositivo, solicite um desafio JSON PublicKeyCredentialRequestOptions da WebAuthn
no servidor do app. Como
o app no novo dispositivo ainda não sabe qual usuário está fazendo login, esse
endpoint precisa ser não autenticado (descoberta sem usuário). Para orientações relacionadas ao servidor, consulte Fazer login com uma chave de acesso e o guia de autenticação
do lado do servidor.
O JSON das opções de recuperação tem a seguinte estrutura:
{
"challenge": "<your app challenge>",
"allowCredentials": [],
"rpId": "<your app server domain>"
}
Configure o JSON das opções de recuperação no servidor da seguinte maneira:
allowCredentials: omita esse campo ou transmita uma matriz vazia ([]) porque a conta do usuário não é conhecida antes da restauração. Depois quegetCredential()retornar, o servidor vai identificar o usuário no camporesponse.userHandle(que corresponde aouser.idregistrado durante a criação) e vai procurar a chave pública porid(credentialId).userVerification: definido como"discouraged"(ou"preferred"). Como a restauração de credenciais é executada silenciosamente sem a interface de biometria ou de bloqueio de tela, o Android substitui"required"por"discouraged", e os dados do autenticador retornados ao servidor têm a flag Usuário verificado (uv) definida comofalse. Configure o verificador do servidor WebAuthn para aceitaruv = falsepara declarações de chave de restauração.
Receber a chave de restauração
Para receber a chave de restauração no novo dispositivo, envolva o requestJson do servidor em um
GetRestoreCredentialOption dentro de um GetCredentialRequest e chame
getCredential() no objeto CredentialManager.
Implemente uma estratégia de restauração em duas etapas para que os usuários façam login, independentemente da execução da restauração de dados do app em segundo plano:
- Nível 1 (restauração em segundo plano no
BackupAgent.onRestoreFinished()): se o backup de dados do app estiver ativado (android:allowBackup="true"), crie uma subclasse deBackupAgente substituaonRestoreFinished(). Não useonRestore(), que é chamado apenas para backups de chave-valor legados. ComoonRestoreFinished()é síncrono egetCredential()é uma funçãosuspenddo Kotlin, faça a ponte da chamada usandorunBlocking(Dispatchers.IO). CapturarNoCredentialExceptionsilenciosamente quando não houver uma chave de restauração. - Nível 2 (substituição em primeiro plano no inicializador
Activity.onCreate()): na primeira inicialização do app, quando não houver uma sessão local, chamegetCredential()delifecycleScopeantes de mostrar a tela de login. Isso processa apps comandroid:allowBackup="false"e se recupera se o dispositivo estava off-line quandoonRestoreFinished()foi executado.
Se você adicionar um novo BackupAgent a um app que já usa o Backup Automático com
android:allowBackup="true", defina também android:fullBackupOnly="true" no elemento
<application> em AndroidManifest.xml para que o Android continue usando
o Backup Automático em vez de mudar para o backup de chave-valor legado.
suspend fun getRestoreKey(
fetchAuthenticationJson: () -> String,
) {
// Fetch the options required to get the restore key
val authenticationJson = fetchAuthenticationJson()
// Create the GetRestoreCredentialOption object
val options = GetRestoreCredentialOption(authenticationJson)
val getRequest = GetCredentialRequest(listOf(options))
try {
val response = credentialManager.getCredential(context, getRequest)
// Type-check and extract the restore credential
val credential = response.credential
if (credential is RestoreCredential) {
val responseJson = credential.authenticationResponseJson
}
} catch (e: NoCredentialException) {
// No restore key is present on this device
}
}
class MyBackupAgent : BackupAgent() {
override fun onRestoreFinished() {
super.onRestoreFinished()
val credentialManager = CredentialManager.create(this)
val restoreFunctions = RestoreCredentialsFunctions(this, credentialManager)
runBlocking(Dispatchers.IO) {
restoreFunctions.getRestoreKey(
fetchAuthenticationJson = {
/* Fetch challenge JSON from server */ ""
}
)
}
}
}
A API Credential Manager retorna um GetCredentialResponse com a propriedade credential como um RestoreCredential (não um PublicKeyCredential) que contém authenticationResponseJson.
Processar a resposta de login
Envie restoreCredential.authenticationResponseJson do app para o servidor da parte confiável para verificar a declaração criptográfica e emitir um novo token de sessão para o usuário. Para detalhes sobre a verificação do lado do servidor, consulte Fazer login com uma
chave de acesso.
Ao projetar o armazenamento de back-end e as políticas de ciclo de vida para chaves de restauração, siga estas diretrizes:
- Diferencie as chaves de restauração das chaves de acesso do usuário: marque as chaves de restauração no seu
banco de dados para que elas fiquem ocultas nas telas de gerenciamento de chaves de acesso voltadas ao usuário e
isentas de verificações de usuário (
uv = true). - Suporte a vários dispositivos ativos por usuário: um usuário pode ter vários dispositivos (como um smartphone e um tablet). Permita várias chaves de restauração ativas por conta de usuário (por exemplo, com chave por dispositivo ou ID de instalação) em vez de substituir uma única linha.
- Defina um TTL adequado e limpe as chaves órfãs: desinstalar um app ou limpar os dados do app exclui a chave de restauração local sem notificar o servidor. Retenha as chaves de restauração por tempo suficiente para sobreviver às janelas de upgrade do dispositivo (mesmo que o usuário saia do dispositivo antigo após a transferência) e remova as chaves desatualizadas com base no TTL de inatividade ou nos limites de chaves por usuário.
Excluir a chave de restauração
O Credential Manager não tem estado e não tem conhecimento da atividade do usuário. Por isso, ele não exclui automaticamente as chaves de restauração após o uso. Para excluir uma chave de restauração, chame o
método clearCredentialState(). Por segurança, exclua a chave sempre que um
usuário sair para que ele não seja conectado silenciosamente na próxima inicialização do app.
As chaves de restauração são removidas apenas nas seguintes situações:
- Ações no nível do sistema: os usuários desinstalam o app ou limpam os dados dele.
- Chamadas no nível do app: exclua a chave de forma programática chamando
clearCredentialState()ao processar a saída do usuário no código do app.
Como um ClearCredentialStateRequest() padrão só limpa o estado da sessão padrão
e deixa a chave de restauração intacta, faça uma limpeza dupla ao sair:
- Chame
credentialManager.clearCredentialState(ClearCredentialStateRequest())para limpar o estado da sessão do provedor de credenciais ativo. - Chame
credentialManager.clearCredentialState()comClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL)para excluir oRestoreCredentialarmazenado. - Exclua o marcador
has_synced_restore_credentiallocal do armazenamento local.
// Clear active credential provider session state
credentialManager.clearCredentialState(ClearCredentialStateRequest())
// When the user logs out, delete the restore key
val clearRequest = ClearCredentialStateRequest(TYPE_CLEAR_RESTORE_CREDENTIAL)
val response = credentialManager.clearCredentialState(clearRequest)
Próximas etapas
- Testar a Restore Credentials: teste os fluxos de restauração na nuvem e de dispositivo para dispositivo (D2D)
usando o Android Studio ou o
adb shell bmgr. - Teste automatizado de backup e restauração: escreva testes de host do JUnit 5
com
BackupRestoreControllerpara verificar as restaurações deCLOUD_ENCRYPTEDeDEVICE_TO_DEVICEna CI.