Fazer a autenticação com os serviços do OAuth2

Diagrama da lógica do token
    de autenticação
Figura 1. Procedimento para receber um token de autenticação válido do gerente de contas do Android.

Para acessar um serviço on-line com segurança, os usuários precisam se autenticar no serviço. Eles precisam fornecer um comprovante de identidade. Para um aplicativo que acessa um serviço de terceiros, o problema de segurança é ainda mais complicado. O usuário não só precisa de autenticação para acessar o serviço, como também o aplicativo precisa ter autorização para agir em nome do usuário.

A forma padrão do setor de lidar com autenticação para serviços de terceiros é o protocolo OAuth2. O OAuth2 oferece um valor único, chamado token de autenticação, que representa a identidade do usuário e a autorização do aplicativo para agir em nome dele. Esta lição mostra como se conectar a um servidor do Google compatível com o OAuth2. Os serviços do Google são usados como exemplo, mas as técnicas demonstradas funcionam em qualquer serviço que ofereça suporte ao protocolo OAuth2.

Usar o OAuth2 é bom para:

  • Conseguir permissão do usuário para acessar um serviço on-line usando a conta dele.
  • autenticar um serviço on-line em nome do usuário;
  • lidar com erros de autenticação.

Coletar informações

Para começar a usar o OAuth2, você precisa conhecer alguns detalhes específicos da API sobre o serviço que está tentando acessar:

  • O URL do serviço que você quer acessar.
  • O escopo de autenticação, que é uma string que define o tipo específico de acesso solicitado pelo app. Por exemplo, o escopo de autenticação para acesso somente leitura ao Google Tarefas é View your tasks, enquanto o escopo de autenticação para acesso de leitura e gravação ao Google Tarefas é Manage your tasks.
  • Um ID e uma chave secreta do cliente, que são strings que identificam seu app para o serviço. Você precisa receber essas strings diretamente do proprietário do serviço. O Google tem um sistema de autoatendimento para receber IDs e secrets do cliente.

Solicitar a permissão de acesso à Internet

Para apps destinados ao Android 6.0 (nível 23 da API) e versões mais recentes, o método getAuthToken() não requer nenhuma permissão. No entanto, para realizar operações no token, você precisa adicionar a permissão INTERNET ao arquivo de manifesto, conforme mostrado no snippet de código a seguir:

<manifest ... >
    <uses-permission android:name="android.permission.INTERNET" />
    ...
</manifest>

Solicitar um token de autenticação

Para receber o token, chame AccountManager.getAuthToken().

Cuidado : como algumas operações de conta podem envolver a comunicação de rede, a maioria dos métodos AccountManager é assíncrono. Isso significa que, em vez de fazer todo o trabalho de autenticação em uma única função, você precisa implementá-lo como uma série de callbacks.

O snippet a seguir mostra como trabalhar com uma série de callbacks para receber o token:

Kotlin

val am: AccountManager = AccountManager.get(this)
val options = Bundle()

am.getAuthToken(
        myAccount_,                     // Account retrieved using getAccountsByType()
        "Manage your tasks",            // Auth scope
        options,                        // Authenticator-specific options
        this,                           // Your activity
        OnTokenAcquired(),              // Callback called when a token is successfully acquired
        Handler(OnError())              // Callback called if an error occurs
)

Java

AccountManager am = AccountManager.get(this);
Bundle options = new Bundle();

am.getAuthToken(
    myAccount_,                     // Account retrieved using getAccountsByType()
    "Manage your tasks",            // Auth scope
    options,                        // Authenticator-specific options
    this,                           // Your activity
    new OnTokenAcquired(),          // Callback called when a token is successfully acquired
    new Handler(new OnError()));    // Callback called if an error occurs

Neste exemplo, OnTokenAcquired é uma classe que implementa AccountManagerCallback. AccountManager chama run() em OnTokenAcquired com um AccountManagerFuture que contém um Bundle. Se a chamada for bem-sucedida, o token estará dentro do Bundle.

Veja como conseguir o token do Bundle:

Kotlin

private class OnTokenAcquired : AccountManagerCallback<Bundle> {

    override fun run(result: AccountManagerFuture<Bundle>) {
        // Get the result of the operation from the AccountManagerFuture.
        val bundle: Bundle = result.getResult()

        // The token is a named value in the bundle. The name of the value
        // is stored in the constant AccountManager.KEY_AUTHTOKEN.
        val token: String = bundle.getString(AccountManager.KEY_AUTHTOKEN)
    }
}

Java

private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
    @Override
    public void run(AccountManagerFuture<Bundle> result) {
        // Get the result of the operation from the AccountManagerFuture.
        Bundle bundle = result.getResult();

        // The token is a named value in the bundle. The name of the value
        // is stored in the constant AccountManager.KEY_AUTHTOKEN.
        String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
        ...
    }
}

Se tudo correr bem, o Bundle conterá um token válido na chave KEY_AUTHTOKEN e está tudo pronto.

Sua primeira solicitação de um token de autenticação pode falhar por vários motivos:

  • Um erro no dispositivo ou na rede causado a falha do AccountManager.
  • O usuário decidiu não conceder ao seu app acesso à conta.
  • As credenciais da conta armazenadas não são suficientes para ter acesso a ela.
  • O token de autenticação armazenado em cache expirou.

Os aplicativos podem processar os dois primeiros casos de maneira trivial, geralmente apenas mostrando uma mensagem de erro ao usuário. Se a rede estiver inativa ou o usuário tiver decidido não conceder acesso, não haverá muito que seu aplicativo possa fazer a respeito. Os dois últimos casos são um pouco mais complicados, porque espera-se que aplicativos bem comportados processem essas falhas automaticamente.

O terceiro caso de falha, ter credenciais insuficientes, é comunicado pelo Bundle que você recebe no seu AccountManagerCallback (OnTokenAcquired do exemplo anterior). Se o Bundle incluir um Intent na chave KEY_INTENT, o autenticador estará informando que precisa interagir diretamente com o usuário antes de poder fornecer um token válido.

Pode haver muitas razões para o autenticador retornar um Intent. Talvez seja a primeira vez que o usuário fez login nessa conta. Talvez a conta do usuário tenha expirado e ele precise fazer login novamente ou as credenciais armazenadas estejam incorretas. Talvez a conta exija autenticação de dois fatores ou precise ativar a câmera para fazer uma verificação de retina. Não importa qual seja o motivo. Se você quiser um token válido, será necessário disparar o Intent para obtê-lo.

Kotlin

private inner class OnTokenAcquired : AccountManagerCallback<Bundle> {

    override fun run(result: AccountManagerFuture<Bundle>) {
        val launch: Intent? = result.getResult().get(AccountManager.KEY_INTENT) as? Intent
        if (launch != null) {
            startActivityForResult(launch, 0)
        }
    }
}

Java

private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
    @Override
    public void run(AccountManagerFuture<Bundle> result) {
        ...
        Intent launch = (Intent) result.getResult().get(AccountManager.KEY_INTENT);
        if (launch != null) {
            startActivityForResult(launch, 0);
            return;
        }
    }
}

Observe que o exemplo usa startActivityForResult(), para que você possa capturar o resultado do Intent implementando onActivityResult() na sua própria atividade. Isso é importante: se você não capturar o resultado da Intent de resposta do autenticador, será impossível dizer se o usuário foi autenticado.

Se o resultado for RESULT_OK, o autenticador atualizou as credenciais armazenadas para que elas sejam suficientes para o nível de acesso solicitado. Chame AccountManager.getAuthToken() novamente para solicitar o novo token de autenticação.

O último caso, em que o token expirou, não é uma falha de AccountManager. A única maneira de descobrir se um token expirou é entrar em contato com o servidor. Seria um desperdício e caro para AccountManager ficar sempre on-line para verificar o estado de todos os tokens. Portanto, essa é uma falha que só pode ser detectada quando um aplicativo como o seu tenta usar o token de autenticação para acessar um serviço on-line.

Conectar ao serviço on-line

O exemplo abaixo mostra como se conectar a um servidor do Google. Como o Google usa o protocolo OAuth2 padrão do setor para autenticar solicitações, as técnicas discutidas aqui são amplamente aplicáveis. No entanto, lembre-se de que cada servidor é diferente. Talvez você precise fazer pequenos ajustes nessas instruções para considerar sua situação específica.

As APIs do Google exigem que você forneça quatro valores a cada solicitação: a chave de API, o ID do cliente, a chave secreta do cliente e a chave de autenticação. Os três primeiros vêm do site do Console de APIs do Google. O último é o valor de string que você recebeu chamando AccountManager.getAuthToken(). Você os transmite para o servidor do Google como parte de uma solicitação HTTP.

Kotlin

val url = URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=$your_api_key")
val conn = url.openConnection() as HttpURLConnection
conn.apply {
    addRequestProperty("client_id", your client id)
    addRequestProperty("client_secret", your client secret)
    setRequestProperty("Authorization", "OAuth $token")
}

Java

URL url = new URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=" + your_api_key);
URLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("client_id", your client id);
conn.addRequestProperty("client_secret", your client secret);
conn.setRequestProperty("Authorization", "OAuth " + token);

Caso a solicitação retorne um código de erro HTTP 401, isso significa que seu token foi negado. Conforme mencionado na última seção, o motivo mais comum para isso é a expiração do token. A correção é simples: chame AccountManager.invalidateAuthToken() e repita o processo de aquisição do token mais uma vez.

Como os tokens expirados são uma ocorrência comum e a correção deles é muito fácil, muitos aplicativos simplesmente supõem que o token expirou antes mesmo de solicitá-lo. Se a renovação de um token for uma operação barata para o servidor, pode ser preferível chamar AccountManager.invalidateAuthToken() antes da primeira chamada para AccountManager.getAuthToken() e evitar a necessidade de solicitar um token de autenticação duas vezes.